summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.gitignore1
-rw-r--r--.gitlab-ci.yml113
-rw-r--r--.ruby-version2
-rw-r--r--CHANGELOG93
-rw-r--r--CONTRIBUTING.md93
-rw-r--r--GITLAB_WORKHORSE_VERSION2
-rw-r--r--Gemfile34
-rw-r--r--Gemfile.lock123
-rw-r--r--PROCESS.md115
-rw-r--r--README.md2
-rwxr-xr-xRakefile3
-rw-r--r--VERSION2
-rw-r--r--app/assets/images/emoji.pngbin832902 -> 263533 bytes
-rw-r--r--app/assets/images/emoji@2x.pngbin0 -> 690504 bytes
-rw-r--r--app/assets/javascripts/activities.js.coffee18
-rw-r--r--app/assets/javascripts/application.js.coffee27
-rw-r--r--app/assets/javascripts/autosave.js.coffee6
-rw-r--r--app/assets/javascripts/awards_handler.coffee19
-rw-r--r--app/assets/javascripts/behaviors/quick_submit.js.coffee47
-rw-r--r--app/assets/javascripts/dashboard.js.coffee31
-rw-r--r--app/assets/javascripts/dispatcher.js.coffee14
-rw-r--r--app/assets/javascripts/issuable_context.js.coffee2
-rw-r--r--app/assets/javascripts/logo.js.coffee2
-rw-r--r--app/assets/javascripts/merge_request_tabs.js.coffee21
-rw-r--r--app/assets/javascripts/milestone.js.coffee23
-rw-r--r--app/assets/javascripts/notes.js.coffee31
-rw-r--r--app/assets/javascripts/pager.js.coffee3
-rw-r--r--app/assets/javascripts/profile.js.coffee57
-rw-r--r--app/assets/javascripts/projects_list.js.coffee49
-rw-r--r--app/assets/javascripts/shortcuts.js.coffee4
-rw-r--r--app/assets/javascripts/shortcuts_issuable.coffee8
-rw-r--r--app/assets/javascripts/sidebar.js.coffee6
-rw-r--r--app/assets/javascripts/user.js.coffee11
-rw-r--r--app/assets/javascripts/user_tabs.js.coffee146
-rw-r--r--app/assets/javascripts/wikis.js.coffee3
-rw-r--r--app/assets/stylesheets/application.scss7
-rw-r--r--app/assets/stylesheets/framework.scss2
-rw-r--r--app/assets/stylesheets/framework/blocks.scss2
-rw-r--r--app/assets/stylesheets/framework/buttons.scss30
-rw-r--r--app/assets/stylesheets/framework/common.scss21
-rw-r--r--app/assets/stylesheets/framework/dropdowns.scss102
-rw-r--r--app/assets/stylesheets/framework/files.scss2
-rw-r--r--app/assets/stylesheets/framework/filters.scss7
-rw-r--r--app/assets/stylesheets/framework/forms.scss12
-rw-r--r--app/assets/stylesheets/framework/gitlab-theme.scss2
-rw-r--r--app/assets/stylesheets/framework/header.scss9
-rw-r--r--app/assets/stylesheets/framework/highlight.scss4
-rw-r--r--app/assets/stylesheets/framework/issue_box.scss20
-rw-r--r--app/assets/stylesheets/framework/jquery.scss4
-rw-r--r--app/assets/stylesheets/framework/lists.scss25
-rw-r--r--app/assets/stylesheets/framework/mixins.scss6
-rw-r--r--app/assets/stylesheets/framework/nav.scss17
-rw-r--r--app/assets/stylesheets/framework/progress.scss5
-rw-r--r--app/assets/stylesheets/framework/selects.scss147
-rw-r--r--app/assets/stylesheets/framework/sidebar.scss112
-rw-r--r--app/assets/stylesheets/framework/timeline.scss4
-rw-r--r--app/assets/stylesheets/framework/tw_bootstrap.scss8
-rw-r--r--app/assets/stylesheets/framework/tw_bootstrap_variables.scss2
-rw-r--r--app/assets/stylesheets/framework/typography.scss2
-rw-r--r--app/assets/stylesheets/framework/variables.scss30
-rw-r--r--app/assets/stylesheets/pages/appearances.scss11
-rw-r--r--app/assets/stylesheets/pages/detail_page.scss9
-rw-r--r--app/assets/stylesheets/pages/emojis.scss3002
-rw-r--r--app/assets/stylesheets/pages/issuable.scss87
-rw-r--r--app/assets/stylesheets/pages/issues.scss23
-rw-r--r--app/assets/stylesheets/pages/labels.scss2
-rw-r--r--app/assets/stylesheets/pages/login.scss4
-rw-r--r--app/assets/stylesheets/pages/merge_requests.scss11
-rw-r--r--app/assets/stylesheets/pages/milestone.scss53
-rw-r--r--app/assets/stylesheets/pages/notes.scss13
-rw-r--r--app/assets/stylesheets/pages/notifications.scss18
-rw-r--r--app/assets/stylesheets/pages/profile.scss120
-rw-r--r--app/assets/stylesheets/pages/projects.scss64
-rw-r--r--app/assets/stylesheets/pages/search.scss10
-rw-r--r--app/assets/stylesheets/pages/snippets.scss24
-rw-r--r--app/assets/stylesheets/pages/todos.scss96
-rw-r--r--app/assets/stylesheets/pages/tree.scss2
-rw-r--r--app/assets/stylesheets/pages/ui_dev_kit.scss11
-rw-r--r--app/assets/stylesheets/pages/wiki.scss5
-rw-r--r--app/controllers/admin/appearances_controller.rb57
-rw-r--r--app/controllers/admin/labels_controller.rb2
-rw-r--r--app/controllers/application_controller.rb11
-rw-r--r--app/controllers/ci/projects_controller.rb2
-rw-r--r--app/controllers/concerns/creates_commit.rb53
-rw-r--r--app/controllers/concerns/issues_action.rb4
-rw-r--r--app/controllers/concerns/merge_requests_action.rb4
-rw-r--r--app/controllers/dashboard/projects_controller.rb6
-rw-r--r--app/controllers/dashboard/todos_controller.rb35
-rw-r--r--app/controllers/emojis_controller.rb6
-rw-r--r--app/controllers/omniauth_callbacks_controller.rb54
-rw-r--r--app/controllers/passwords_controller.rb8
-rw-r--r--app/controllers/profiles/keys_controller.rb8
-rw-r--r--app/controllers/profiles/two_factor_auths_controller.rb12
-rw-r--r--app/controllers/profiles_controller.rb3
-rw-r--r--app/controllers/projects/avatars_controller.rb7
-rw-r--r--app/controllers/projects/badges_controller.rb13
-rw-r--r--app/controllers/projects/blob_controller.rb2
-rw-r--r--app/controllers/projects/builds_controller.rb6
-rw-r--r--app/controllers/projects/commit_controller.rb57
-rw-r--r--app/controllers/projects/compare_controller.rb12
-rw-r--r--app/controllers/projects/forks_controller.rb24
-rw-r--r--app/controllers/projects/imports_controller.rb12
-rw-r--r--app/controllers/projects/issues_controller.rb1
-rw-r--r--app/controllers/projects/labels_controller.rb2
-rw-r--r--app/controllers/projects/merge_requests_controller.rb8
-rw-r--r--app/controllers/projects/milestones_controller.rb3
-rw-r--r--app/controllers/projects/raw_controller.rb15
-rw-r--r--app/controllers/projects/refs_controller.rb6
-rw-r--r--app/controllers/projects/repositories_controller.rb4
-rw-r--r--app/controllers/projects/tags_controller.rb7
-rw-r--r--app/controllers/projects_controller.rb2
-rw-r--r--app/controllers/search_controller.rb2
-rw-r--r--app/controllers/sessions_controller.rb18
-rw-r--r--app/controllers/uploads_controller.rb5
-rw-r--r--app/controllers/users_controller.rb70
-rw-r--r--app/finders/issuable_finder.rb26
-rw-r--r--app/finders/todos_finder.rb129
-rw-r--r--app/helpers/appearances_helper.rb28
-rw-r--r--app/helpers/application_helper.rb76
-rw-r--r--app/helpers/auth_helper.rb4
-rw-r--r--app/helpers/blob_helper.rb43
-rw-r--r--app/helpers/commits_helper.rb31
-rw-r--r--app/helpers/diff_helper.rb38
-rw-r--r--app/helpers/events_helper.rb4
-rw-r--r--app/helpers/gitlab_markdown_helper.rb2
-rw-r--r--app/helpers/icons_helper.rb11
-rw-r--r--app/helpers/issuables_helper.rb37
-rw-r--r--app/helpers/labels_helper.rb15
-rw-r--r--app/helpers/milestones_helper.rb36
-rw-r--r--app/helpers/nav_helper.rb19
-rw-r--r--app/helpers/projects_helper.rb8
-rw-r--r--app/helpers/sorting_helper.rb18
-rw-r--r--app/helpers/todos_helper.rb87
-rw-r--r--app/helpers/tree_helper.rb3
-rw-r--r--app/models/ability.rb2
-rw-r--r--app/models/appearance.rb9
-rw-r--r--app/models/blob.rb37
-rw-r--r--app/models/ci/build.rb52
-rw-r--r--app/models/ci/runner.rb6
-rw-r--r--app/models/commit.rb54
-rw-r--r--app/models/commit_status.rb16
-rw-r--r--app/models/concerns/issuable.rb40
-rw-r--r--app/models/concerns/milestoneish.rb25
-rw-r--r--app/models/diff_line.rb3
-rw-r--r--app/models/global_label.rb7
-rw-r--r--app/models/global_milestone.rb55
-rw-r--r--app/models/label.rb68
-rw-r--r--app/models/merge_request.rb53
-rw-r--r--app/models/merge_request_diff.rb86
-rw-r--r--app/models/milestone.rb22
-rw-r--r--app/models/note.rb52
-rw-r--r--app/models/project.rb47
-rw-r--r--app/models/project_services/jira_service.rb6
-rw-r--r--app/models/project_services/pushover_service.rb2
-rw-r--r--app/models/project_team.rb2
-rw-r--r--app/models/project_wiki.rb2
-rw-r--r--app/models/repository.rb124
-rw-r--r--app/models/todo.rb53
-rw-r--r--app/models/user.rb24
-rw-r--r--app/services/archive_repository_service.rb23
-rw-r--r--app/services/base_service.rb4
-rw-r--r--app/services/ci/create_builds_service.rb1
-rw-r--r--app/services/commits/revert_service.rb58
-rw-r--r--app/services/compare_service.rb12
-rw-r--r--app/services/git_push_service.rb118
-rw-r--r--app/services/git_tag_push_service.rb2
-rw-r--r--app/services/issuable_base_service.rb15
-rw-r--r--app/services/issues/close_service.rb2
-rw-r--r--app/services/issues/create_service.rb1
-rw-r--r--app/services/issues/update_service.rb12
-rw-r--r--app/services/merge_requests/build_service.rb22
-rw-r--r--app/services/merge_requests/close_service.rb1
-rw-r--r--app/services/merge_requests/create_service.rb3
-rw-r--r--app/services/merge_requests/merge_service.rb3
-rw-r--r--app/services/merge_requests/merge_when_build_succeeds_service.rb27
-rw-r--r--app/services/merge_requests/update_service.rb12
-rw-r--r--app/services/notes/create_service.rb2
-rw-r--r--app/services/notes/post_process_service.rb2
-rw-r--r--app/services/notes/update_service.rb4
-rw-r--r--app/services/projects/destroy_service.rb12
-rw-r--r--app/services/system_note_service.rb21
-rw-r--r--app/services/todo_service.rb170
-rw-r--r--app/uploaders/avatar_uploader.rb11
-rw-r--r--app/validators/url_validator.rb3
-rw-r--r--app/views/abuse_reports/new.html.haml4
-rw-r--r--app/views/admin/appearances/_form.html.haml58
-rw-r--r--app/views/admin/appearances/preview.html.haml29
-rw-r--r--app/views/admin/appearances/show.html.haml7
-rw-r--r--app/views/admin/broadcast_messages/_form.html.haml4
-rw-r--r--app/views/admin/labels/_form.html.haml4
-rw-r--r--app/views/admin/labels/_label.html.haml10
-rw-r--r--app/views/admin/users/keys.html.haml2
-rw-r--r--app/views/dashboard/_projects_head.html.haml2
-rw-r--r--app/views/dashboard/milestones/_issue.html.haml10
-rw-r--r--app/views/dashboard/milestones/_issues.html.haml6
-rw-r--r--app/views/dashboard/milestones/_merge_request.html.haml10
-rw-r--r--app/views/dashboard/milestones/_merge_requests.html.haml6
-rw-r--r--app/views/dashboard/milestones/_milestone.html.haml31
-rw-r--r--app/views/dashboard/milestones/show.html.haml106
-rw-r--r--app/views/dashboard/projects/_projects.html.haml7
-rw-r--r--app/views/dashboard/projects/_zero_authorized_projects.html.haml4
-rw-r--r--app/views/dashboard/projects/index.html.haml2
-rw-r--r--app/views/dashboard/todos/_todo.html.haml21
-rw-r--r--app/views/dashboard/todos/index.html.haml62
-rw-r--r--app/views/devise/sessions/new.html.haml6
-rw-r--r--app/views/emojis/index.html.haml9
-rw-r--r--app/views/events/event/_common.html.haml2
-rw-r--r--app/views/explore/projects/_dropdown.html.haml2
-rw-r--r--app/views/explore/projects/_filter.html.haml73
-rw-r--r--app/views/explore/projects/_projects.html.haml7
-rw-r--r--app/views/explore/projects/index.html.haml4
-rw-r--r--app/views/groups/_projects.html.haml14
-rw-r--r--app/views/groups/group_members/_group_member.html.haml3
-rw-r--r--app/views/groups/milestones/_issue.html.haml10
-rw-r--r--app/views/groups/milestones/_issues.html.haml6
-rw-r--r--app/views/groups/milestones/_merge_request.html.haml10
-rw-r--r--app/views/groups/milestones/_merge_requests.html.haml6
-rw-r--r--app/views/groups/milestones/_milestone.html.haml34
-rw-r--r--app/views/groups/milestones/new.html.haml6
-rw-r--r--app/views/groups/milestones/show.html.haml114
-rw-r--r--app/views/groups/show.html.haml7
-rw-r--r--app/views/help/_shortcuts.html.haml8
-rw-r--r--app/views/help/ui.html.haml216
-rw-r--r--app/views/layouts/_head.html.haml1
-rw-r--r--app/views/layouts/_page.html.haml2
-rw-r--r--app/views/layouts/_search.html.haml2
-rw-r--r--app/views/layouts/application.html.haml6
-rw-r--r--app/views/layouts/ci/_page.html.haml2
-rw-r--r--app/views/layouts/header/_default.html.haml47
-rw-r--r--app/views/layouts/header/_public.html.haml10
-rw-r--r--app/views/layouts/nav/_admin.html.haml5
-rw-r--r--app/views/layouts/nav/_dashboard.html.haml18
-rw-r--r--app/views/profiles/_event_table.html.haml28
-rw-r--r--app/views/profiles/applications.html.haml2
-rw-r--r--app/views/profiles/audit_log.html.haml13
-rw-r--r--app/views/profiles/emails/index.html.haml93
-rw-r--r--app/views/profiles/keys/_form.html.haml14
-rw-r--r--app/views/profiles/keys/_key.html.haml25
-rw-r--r--app/views/profiles/keys/_key_details.html.haml2
-rw-r--r--app/views/profiles/keys/_key_table.html.haml20
-rw-r--r--app/views/profiles/keys/index.html.haml29
-rw-r--r--app/views/profiles/keys/new.html.haml17
-rw-r--r--app/views/profiles/notifications/_settings.html.haml4
-rw-r--r--app/views/profiles/notifications/show.html.haml123
-rw-r--r--app/views/profiles/passwords/edit.html.haml51
-rw-r--r--app/views/profiles/preferences/show.html.haml99
-rw-r--r--app/views/profiles/show.html.haml178
-rw-r--r--app/views/projects/_home_panel.html.haml11
-rw-r--r--app/views/projects/blame/show.html.haml6
-rw-r--r--app/views/projects/blob/_blob.html.haml12
-rw-r--r--app/views/projects/blob/_editor.html.haml2
-rw-r--r--app/views/projects/blob/_image.html.haml9
-rw-r--r--app/views/projects/blob/_new_dir.html.haml2
-rw-r--r--app/views/projects/blob/_remove.html.haml2
-rw-r--r--app/views/projects/blob/_upload.html.haml2
-rw-r--r--app/views/projects/blob/edit.html.haml2
-rw-r--r--app/views/projects/blob/new.html.haml2
-rw-r--r--app/views/projects/builds/index.html.haml4
-rw-r--r--app/views/projects/builds/show.html.haml78
-rw-r--r--app/views/projects/commit/_commit_box.html.haml2
-rw-r--r--app/views/projects/commit/_revert.html.haml31
-rw-r--r--app/views/projects/commit/show.html.haml2
-rw-r--r--app/views/projects/diffs/_diffs.html.haml11
-rw-r--r--app/views/projects/diffs/_image.html.haml16
-rw-r--r--app/views/projects/diffs/_text_file.html.haml6
-rw-r--r--app/views/projects/diffs/_warning.html.haml13
-rw-r--r--app/views/projects/edit.html.haml6
-rw-r--r--app/views/projects/empty.html.haml2
-rw-r--r--app/views/projects/forks/_projects.html.haml2
-rw-r--r--app/views/projects/forks/index.html.haml31
-rw-r--r--app/views/projects/issues/_discussion.html.haml4
-rw-r--r--app/views/projects/issues/_form.html.haml2
-rw-r--r--app/views/projects/issues/_issue.html.haml13
-rw-r--r--app/views/projects/issues/show.html.haml54
-rw-r--r--app/views/projects/issues/update.js.haml2
-rw-r--r--app/views/projects/labels/_form.html.haml8
-rw-r--r--app/views/projects/labels/_label.html.haml7
-rw-r--r--app/views/projects/merge_requests/_merge_request.html.haml15
-rw-r--r--app/views/projects/merge_requests/_new_compare.html.haml29
-rw-r--r--app/views/projects/merge_requests/_new_submit.html.haml10
-rw-r--r--app/views/projects/merge_requests/_show.html.haml8
-rw-r--r--app/views/projects/merge_requests/show/_diffs.html.haml2
-rw-r--r--app/views/projects/merge_requests/show/_mr_title.html.haml28
-rw-r--r--app/views/projects/merge_requests/update.js.haml6
-rw-r--r--app/views/projects/merge_requests/widget/_merged.html.haml14
-rw-r--r--app/views/projects/merge_requests/widget/_merged_buttons.haml11
-rw-r--r--app/views/projects/merge_requests/widget/open/_accept.html.haml2
-rw-r--r--app/views/projects/milestones/_form.html.haml6
-rw-r--r--app/views/projects/milestones/_issue.html.haml9
-rw-r--r--app/views/projects/milestones/_issues.html.haml6
-rw-r--r--app/views/projects/milestones/_merge_request.html.haml8
-rw-r--r--app/views/projects/milestones/_merge_requests.html.haml6
-rw-r--r--app/views/projects/milestones/_milestone.html.haml36
-rw-r--r--app/views/projects/milestones/show.html.haml90
-rw-r--r--app/views/projects/notes/_edit_form.html.haml4
-rw-r--r--app/views/projects/notes/_form.html.haml4
-rw-r--r--app/views/projects/notes/_note.html.haml2
-rw-r--r--app/views/projects/refs/logs_tree.js.haml7
-rw-r--r--app/views/projects/releases/edit.html.haml4
-rw-r--r--app/views/projects/tags/destroy.js.haml3
-rw-r--r--app/views/projects/tags/new.html.haml4
-rw-r--r--app/views/projects/wikis/_form.html.haml4
-rw-r--r--app/views/projects/wikis/_main_links.html.haml23
-rw-r--r--app/views/projects/wikis/_nav.html.haml2
-rw-r--r--app/views/projects/wikis/_new.html.haml13
-rw-r--r--app/views/projects/wikis/edit.html.haml22
-rw-r--r--app/views/projects/wikis/history.html.haml13
-rw-r--r--app/views/projects/wikis/pages.html.haml13
-rw-r--r--app/views/projects/wikis/show.html.haml11
-rw-r--r--app/views/search/_form.html.haml2
-rw-r--r--app/views/search/_results.html.haml2
-rw-r--r--app/views/search/results/_snippet_blob.html.haml2
-rw-r--r--app/views/shared/_commit_message_container.html.haml2
-rw-r--r--app/views/shared/_issues.html.haml2
-rw-r--r--app/views/shared/_label_row.html.haml4
-rw-r--r--app/views/shared/_merge_requests.html.haml2
-rw-r--r--app/views/shared/_no_ssh.html.haml2
-rw-r--r--app/views/shared/_project_limit.html.haml2
-rw-r--r--app/views/shared/_sort_dropdown.html.haml4
-rw-r--r--app/views/shared/groups/_group.html.haml6
-rw-r--r--app/views/shared/groups/_list.html.haml6
-rw-r--r--app/views/shared/issuable/_filter.html.haml4
-rw-r--r--app/views/shared/issuable/_form.html.haml4
-rw-r--r--app/views/shared/issuable/_participants.html.haml5
-rw-r--r--app/views/shared/issuable/_sidebar.html.haml50
-rw-r--r--app/views/shared/milestones/_issuable.html.haml25
-rw-r--r--app/views/shared/milestones/_issuables.html.haml16
-rw-r--r--app/views/shared/milestones/_issues_tab.html.haml10
-rw-r--r--app/views/shared/milestones/_labels_tab.html.haml18
-rw-r--r--app/views/shared/milestones/_merge_requests_tab.haml12
-rw-r--r--app/views/shared/milestones/_milestone.html.haml45
-rw-r--r--app/views/shared/milestones/_participants_tab.html.haml8
-rw-r--r--app/views/shared/milestones/_summary.html.haml28
-rw-r--r--app/views/shared/milestones/_tabs.html.haml30
-rw-r--r--app/views/shared/milestones/_top.html.haml58
-rw-r--r--app/views/shared/projects/_list.html.haml28
-rw-r--r--app/views/shared/projects/_project.html.haml13
-rw-r--r--app/views/shared/snippets/_snippet.html.haml7
-rw-r--r--app/views/snippets/_snippets.html.haml2
-rw-r--r--app/views/users/show.html.haml199
-rw-r--r--app/views/votes/_votes_block.html.haml9
-rw-r--r--app/workers/irker_worker.rb2
-rw-r--r--app/workers/post_receive.rb2
-rw-r--r--app/workers/repository_fork_worker.rb1
-rw-r--r--app/workers/repository_import_worker.rb1
-rw-r--r--config/application.rb20
-rw-r--r--config/gitlab.yml.example11
-rw-r--r--config/initializers/1_settings.rb7
-rw-r--r--config/initializers/2_app.rb4
-rw-r--r--config/initializers/relative_url.rb.sample10
-rw-r--r--config/initializers/sentry.rb1
-rw-r--r--config/mail_room.yml85
-rw-r--r--config/newrelic.yml16
-rw-r--r--config/routes.rb34
-rw-r--r--config/sidekiq.yml.example2
-rw-r--r--db/fixtures/production/001_admin.rb55
-rw-r--r--db/migrate/20160129155512_add_merge_commit_sha_to_merge_requests.rb5
-rw-r--r--db/migrate/20160202091601_add_erasable_to_ci_build.rb6
-rw-r--r--db/migrate/20160204144558_add_real_size_to_merge_request_diffs.rb5
-rw-r--r--db/migrate/20160212123307_create_tasks.rb14
-rw-r--r--db/migrate/20160217100506_add_description_to_label.rb5
-rw-r--r--db/migrate/20160217174422_add_note_to_tasks.rb5
-rw-r--r--db/migrate/20160220123949_rename_tasks_to_todos.rb5
-rw-r--r--db/migrate/20160222153918_create_appearances_ce.rb14
-rw-r--r--db/schema.rb39
-rw-r--r--doc/README.md44
-rw-r--r--doc/api/README.md1
-rw-r--r--doc/api/builds.md88
-rw-r--r--doc/api/merge_requests.md2
-rw-r--r--doc/api/runners.md322
-rw-r--r--doc/ci/README.md72
-rw-r--r--doc/ci/api/runners.md6
-rw-r--r--doc/ci/examples/README.md16
-rw-r--r--doc/ci/examples/php.md (renamed from doc/ci/languages/php.md)0
-rw-r--r--doc/ci/languages/README.md7
-rw-r--r--doc/ci/quick_start/README.md72
-rw-r--r--doc/ci/services/README.md12
-rw-r--r--doc/ci/variables/README.md6
-rw-r--r--doc/ci/yaml/README.md35
-rw-r--r--doc/customization/branded_login_page.md19
-rw-r--r--doc/customization/branded_login_page/appearance.pngbin0 -> 365120 bytes
-rw-r--r--doc/customization/branded_login_page/custom_sign_in.pngbin0 -> 314111 bytes
-rw-r--r--doc/customization/branded_login_page/default_login_page.pngbin0 -> 292731 bytes
-rw-r--r--doc/customization/welcome_message.md8
-rw-r--r--doc/development/README.md11
-rw-r--r--doc/development/architecture.md2
-rw-r--r--doc/development/ci_setup.md2
-rw-r--r--doc/development/doc_styleguide.md16
-rw-r--r--doc/development/gotchas.md103
-rw-r--r--doc/development/migration_style_guide.md8
-rw-r--r--doc/gitlab-basics/basicsimages/compare_branches.png (renamed from doc/gitlab-basics/basicsimages/compare_braches.png)bin1624 -> 1624 bytes
-rw-r--r--doc/install/installation.md51
-rw-r--r--doc/install/redis.md60
-rw-r--r--doc/install/relative_url.md20
-rw-r--r--doc/install/requirements.md6
-rw-r--r--doc/integration/README.md31
-rw-r--r--doc/integration/ldap.md22
-rw-r--r--doc/integration/omniauth.md86
-rw-r--r--doc/integration/saml.md139
-rw-r--r--doc/legal/individual_contributor_license_agreement.md2
-rw-r--r--doc/markdown/markdown.md1
-rw-r--r--doc/permissions/permissions.md4
-rw-r--r--doc/project_services/builds_emails.md16
-rw-r--r--doc/project_services/img/builds_emails_service.pngbin0 -> 41222 bytes
-rw-r--r--doc/project_services/jira.md13
-rw-r--r--doc/project_services/project_services.md2
-rw-r--r--doc/update/8.3-to-8.4.md21
-rw-r--r--doc/update/8.4-to-8.5.md26
-rw-r--r--doc/web_hooks/web_hooks.md240
-rw-r--r--doc/workflow/README.md2
-rw-r--r--doc/workflow/gitlab_flow.md13
-rw-r--r--doc/workflow/img/revert_changes_commit.pngbin0 -> 360098 bytes
-rw-r--r--doc/workflow/img/revert_changes_commit_modal.pngbin0 -> 327932 bytes
-rw-r--r--doc/workflow/img/revert_changes_mr.pngbin0 -> 367881 bytes
-rw-r--r--doc/workflow/img/revert_changes_mr_modal.pngbin0 -> 335010 bytes
-rw-r--r--doc/workflow/img/todos_icon.pngbin0 -> 7394 bytes
-rw-r--r--doc/workflow/img/todos_index.pngbin0 -> 184839 bytes
-rw-r--r--doc/workflow/img/web_editor_new_branch_dropdown.pngbin0 -> 34233 bytes
-rw-r--r--doc/workflow/img/web_editor_new_branch_page.pngbin0 -> 21618 bytes
-rw-r--r--doc/workflow/img/web_editor_new_directory_dialog.pngbin0 -> 26145 bytes
-rw-r--r--doc/workflow/img/web_editor_new_directory_dropdown.pngbin0 -> 33714 bytes
-rw-r--r--doc/workflow/img/web_editor_new_file_dropdown.pngbin0 -> 34978 bytes
-rw-r--r--doc/workflow/img/web_editor_new_file_editor.pngbin0 -> 128658 bytes
-rw-r--r--doc/workflow/img/web_editor_new_push_widget.pngbin0 -> 11220 bytes
-rw-r--r--doc/workflow/img/web_editor_new_tag_dropdown.pngbin0 -> 33753 bytes
-rw-r--r--doc/workflow/img/web_editor_new_tag_page.pngbin0 -> 75536 bytes
-rw-r--r--doc/workflow/img/web_editor_start_new_merge_request.pngbin0 -> 14352 bytes
-rw-r--r--doc/workflow/img/web_editor_upload_file_dialog.pngbin0 -> 46219 bytes
-rw-r--r--doc/workflow/img/web_editor_upload_file_dropdown.pngbin0 -> 34835 bytes
-rw-r--r--doc/workflow/importing/migrating_from_svn.md1
-rw-r--r--doc/workflow/lfs/lfs_administration.md8
-rw-r--r--doc/workflow/lfs/manage_large_binaries_with_git_lfs.md85
-rw-r--r--doc/workflow/revert_changes.md64
-rw-r--r--doc/workflow/shortcuts.pngbin48782 -> 25005 bytes
-rw-r--r--doc/workflow/todos.md73
-rw-r--r--doc/workflow/web_editor.md126
-rw-r--r--doc/workflow/web_editor/edit_file.pngbin89039 -> 0 bytes
-rw-r--r--doc/workflow/web_editor/empty_project.pngbin122296 -> 0 bytes
-rw-r--r--doc/workflow/web_editor/new_file.pngbin85526 -> 0 bytes
-rw-r--r--doc/workflow/web_editor/show_file.pngbin111479 -> 0 bytes
-rw-r--r--features/admin/appearance.feature37
-rw-r--r--features/dashboard/event_filters.feature12
-rw-r--r--features/dashboard/todos.feature38
-rw-r--r--features/explore/projects.feature1
-rw-r--r--features/group/milestones.feature17
-rw-r--r--features/groups.feature12
-rw-r--r--features/login_form.feature5
-rw-r--r--features/profile/ssh_keys.feature2
-rw-r--r--features/project/badges/build.feature5
-rw-r--r--features/project/builds/summary.feature11
-rw-r--r--features/project/commits/revert.feature28
-rw-r--r--features/project/fork.feature7
-rw-r--r--features/project/issues/award_emoji.feature11
-rw-r--r--features/project/issues/issues.feature27
-rw-r--r--features/project/merge_requests.feature35
-rw-r--r--features/project/merge_requests/revert.feature30
-rw-r--r--features/project/milestone.feature24
-rw-r--r--features/project/project.feature6
-rw-r--r--features/search.feature22
-rw-r--r--features/steps/admin/appearance.rb72
-rw-r--r--features/steps/dashboard/todos.rb128
-rw-r--r--features/steps/explore/projects.rb2
-rw-r--r--features/steps/group/milestones.rb47
-rw-r--r--features/steps/groups.rb29
-rw-r--r--features/steps/login_form.rb25
-rw-r--r--features/steps/profile/profile.rb30
-rw-r--r--features/steps/profile/ssh_keys.rb4
-rw-r--r--features/steps/project/badges/build.rb4
-rw-r--r--features/steps/project/builds/summary.rb26
-rw-r--r--features/steps/project/commits/commits.rb7
-rw-r--r--features/steps/project/commits/revert.rb40
-rw-r--r--features/steps/project/fork.rb6
-rw-r--r--features/steps/project/issues/award_emoji.rb31
-rw-r--r--features/steps/project/issues/issues.rb66
-rw-r--r--features/steps/project/issues/milestones.rb2
-rw-r--r--features/steps/project/merge_requests.rb79
-rw-r--r--features/steps/project/merge_requests/acceptance.rb2
-rw-r--r--features/steps/project/merge_requests/revert.rb56
-rw-r--r--features/steps/project/project.rb10
-rw-r--r--features/steps/project/project_milestone.rb59
-rw-r--r--features/steps/project/source/markdown_render.rb18
-rw-r--r--features/steps/project/wiki.rb6
-rw-r--r--features/steps/search.rb9
-rw-r--r--features/steps/shared/builds.rb10
-rw-r--r--features/steps/shared/issuable.rb40
-rw-r--r--features/steps/shared/note.rb7
-rw-r--r--features/steps/shared/paths.rb35
-rw-r--r--features/steps/shared/user.rb16
-rw-r--r--features/support/capybara.rb2
-rw-r--r--features/user.feature13
-rw-r--r--fixtures/emojis/aliases.json138
-rwxr-xr-xfixtures/emojis/generate_aliases.rb18
-rw-r--r--fixtures/emojis/index.json20099
-rw-r--r--lib/api/api.rb1
-rw-r--r--lib/api/builds.rb46
-rw-r--r--lib/api/commit_statuses.rb10
-rw-r--r--lib/api/commits.rb6
-rw-r--r--lib/api/entities.rb29
-rw-r--r--lib/api/internal.rb13
-rw-r--r--lib/api/repositories.rb7
-rw-r--r--lib/api/runners.rb175
-rw-r--r--lib/banzai.rb4
-rw-r--r--lib/banzai/filter/abstract_reference_filter.rb2
-rw-r--r--lib/banzai/filter/emoji_filter.rb3
-rw-r--r--lib/banzai/filter/gollum_tags_filter.rb37
-rw-r--r--lib/banzai/filter/label_reference_filter.rb79
-rw-r--r--lib/banzai/filter/sanitization_filter.rb10
-rw-r--r--lib/banzai/filter/yaml_front_matter_filter.rb28
-rw-r--r--lib/banzai/filter_array.rb27
-rw-r--r--lib/banzai/pipeline/base_pipeline.rb2
-rw-r--r--lib/banzai/pipeline/broadcast_message_pipeline.rb2
-rw-r--r--lib/banzai/pipeline/combined_pipeline.rb2
-rw-r--r--lib/banzai/pipeline/gfm_pipeline.rb2
-rw-r--r--lib/banzai/pipeline/plain_markdown_pipeline.rb2
-rw-r--r--lib/banzai/pipeline/post_process_pipeline.rb2
-rw-r--r--lib/banzai/pipeline/pre_process_pipeline.rb17
-rw-r--r--lib/banzai/pipeline/reference_extraction_pipeline.rb2
-rw-r--r--lib/banzai/pipeline/single_line_pipeline.rb2
-rw-r--r--lib/banzai/pipeline/wiki_pipeline.rb3
-rw-r--r--lib/banzai/renderer.rb6
-rw-r--r--lib/ci/api/builds.rb6
-rw-r--r--lib/ci/status.rb4
-rw-r--r--lib/gitlab/compare_result.rb9
-rw-r--r--lib/gitlab/database.rb8
-rw-r--r--lib/gitlab/diff/file.rb2
-rw-r--r--lib/gitlab/diff/parser.rb79
-rw-r--r--lib/gitlab/email/message/repository_push.rb2
-rw-r--r--lib/gitlab/gitlab_import/importer.rb2
-rw-r--r--lib/gitlab/ldap/user.rb4
-rw-r--r--lib/gitlab/note_data_builder.rb11
-rw-r--r--lib/gitlab/o_auth/user.rb13
-rw-r--r--lib/gitlab/push_data_builder.rb18
-rw-r--r--lib/gitlab/saml/user.rb47
-rw-r--r--lib/gitlab/workhorse.rb27
-rw-r--r--lib/tasks/brakeman.rake2
-rw-r--r--lib/tasks/cache.rake18
-rw-r--r--lib/tasks/gitlab/check.rake24
-rwxr-xr-xscripts/notify_slack.sh13
-rw-r--r--spec/config/mail_room_spec.rb56
-rw-r--r--spec/controllers/autocomplete_controller_spec.rb2
-rw-r--r--spec/controllers/ci/projects_controller_spec.rb53
-rw-r--r--spec/controllers/commit_controller_spec.rb51
-rw-r--r--spec/controllers/namespaces_controller_spec.rb2
-rw-r--r--spec/controllers/profiles/avatars_controller_spec.rb2
-rw-r--r--spec/controllers/profiles/keys_controller_spec.rb (renamed from spec/controllers/profile_keys_controller_spec.rb)0
-rw-r--r--spec/controllers/projects/blame_controller_spec.rb (renamed from spec/controllers/blame_controller_spec.rb)0
-rw-r--r--spec/controllers/projects/branches_controller_spec.rb (renamed from spec/controllers/branches_controller_spec.rb)0
-rw-r--r--spec/controllers/projects/commit_controller_spec.rb37
-rw-r--r--spec/controllers/projects/commits_controller_spec.rb (renamed from spec/controllers/commits_controller_spec.rb)0
-rw-r--r--spec/controllers/projects/compare_controller_spec.rb8
-rw-r--r--spec/controllers/projects/forks_controller_spec.rb72
-rw-r--r--spec/controllers/projects/imports_controller_spec.rb12
-rw-r--r--spec/controllers/projects/merge_requests_controller_spec.rb34
-rw-r--r--spec/controllers/projects/repositories_controller_spec.rb11
-rw-r--r--spec/controllers/sent_notifications_controller_spec.rb (renamed from spec/controllers/sent_notification_controller_spec.rb)0
-rw-r--r--spec/controllers/uploads_controller_spec.rb2
-rw-r--r--spec/controllers/users_controller_spec.rb18
-rw-r--r--spec/factories.rb222
-rw-r--r--spec/factories/abuse_reports.rb2
-rw-r--r--spec/factories/appearances.rb8
-rw-r--r--spec/factories/broadcast_messages.rb2
-rw-r--r--spec/factories/ci/builds.rb41
-rw-r--r--spec/factories/ci/commits.rb1
-rw-r--r--spec/factories/ci/runner_projects.rb2
-rw-r--r--spec/factories/ci/runners.rb12
-rw-r--r--spec/factories/ci/trigger_requests.rb2
-rw-r--r--spec/factories/ci/triggers.rb2
-rw-r--r--spec/factories/ci/variables.rb2
-rw-r--r--spec/factories/deploy_keys_projects.rb6
-rw-r--r--spec/factories/emails.rb6
-rw-r--r--spec/factories/events.rb10
-rw-r--r--spec/factories/forked_project_links.rb2
-rw-r--r--spec/factories/groups.rb7
-rw-r--r--spec/factories/identities.rb6
-rw-r--r--spec/factories/issues.rb18
-rw-r--r--spec/factories/keys.rb24
-rw-r--r--spec/factories/label_links.rb2
-rw-r--r--spec/factories/labels.rb2
-rw-r--r--spec/factories/lfs_objects.rb2
-rw-r--r--spec/factories/lfs_objects_projects.rb2
-rw-r--r--spec/factories/merge_requests.rb11
-rw-r--r--spec/factories/milestones.rb12
-rw-r--r--spec/factories/namespaces.rb7
-rw-r--r--spec/factories/notes.rb16
-rw-r--r--spec/factories/personal_snippets.rb15
-rw-r--r--spec/factories/project_hooks.rb5
-rw-r--r--spec/factories/project_members.rb27
-rw-r--r--spec/factories/project_snippets.rb9
-rw-r--r--spec/factories/protected_branches.rb6
-rw-r--r--spec/factories/releases.rb2
-rw-r--r--spec/factories/sent_notifications.rb8
-rw-r--r--spec/factories/service_hooks.rb6
-rw-r--r--spec/factories/services.rb5
-rw-r--r--spec/factories/snippets.rb16
-rw-r--r--spec/factories/spam_logs.rb2
-rw-r--r--spec/factories/system_hooks.rb5
-rw-r--r--spec/factories/todos.rb34
-rw-r--r--spec/factories/users.rb50
-rw-r--r--spec/features/issues/filter_by_milestone_spec.rb4
-rw-r--r--spec/features/login_spec.rb26
-rw-r--r--spec/features/runners_spec.rb10
-rw-r--r--spec/fixtures/mail_room_disabled.yml11
-rw-r--r--spec/fixtures/mail_room_enabled.yml11
-rw-r--r--spec/fixtures/markdown.md.erb2
-rw-r--r--spec/helpers/application_helper_spec.rb6
-rw-r--r--spec/helpers/diff_helper_spec.rb57
-rw-r--r--spec/helpers/gitlab_markdown_helper_spec.rb2
-rw-r--r--spec/javascripts/fixtures/behaviors/quick_submit.html.haml6
-rw-r--r--spec/lib/banzai/filter/emoji_filter_spec.rb4
-rw-r--r--spec/lib/banzai/filter/gollum_tags_filter_spec.rb15
-rw-r--r--spec/lib/banzai/filter/label_reference_filter_spec.rb27
-rw-r--r--spec/lib/banzai/filter/sanitization_filter_spec.rb16
-rw-r--r--spec/lib/banzai/filter/yaml_front_matter_filter_spec.rb53
-rw-r--r--spec/lib/banzai/filter_array_spec.rb39
-rw-r--r--spec/lib/banzai/pipeline/wiki_pipeline_spec.rb53
-rw-r--r--spec/lib/ci/status_spec.rb71
-rw-r--r--spec/lib/gitlab/database_spec.rb32
-rw-r--r--spec/lib/gitlab/diff/parser_spec.rb2
-rw-r--r--spec/lib/gitlab/email/message/repository_push_spec.rb2
-rw-r--r--spec/lib/gitlab/note_data_builder_spec.rb30
-rw-r--r--spec/lib/gitlab/o_auth/user_spec.rb26
-rw-r--r--spec/lib/gitlab/push_data_builder_spec.rb27
-rw-r--r--spec/lib/gitlab/saml/user_spec.rb271
-rw-r--r--spec/lib/gitlab/workhorse_spec.rb18
-rw-r--r--spec/models/appearance_spec.rb10
-rw-r--r--spec/models/blob_spec.rb81
-rw-r--r--spec/models/build_spec.rb133
-rw-r--r--spec/models/ci/commit_spec.rb29
-rw-r--r--spec/models/ci/runner_spec.rb14
-rw-r--r--spec/models/commit_spec.rb34
-rw-r--r--spec/models/concerns/issuable_spec.rb23
-rw-r--r--spec/models/hooks/project_hook_spec.rb4
-rw-r--r--spec/models/hooks/web_hook_spec.rb20
-rw-r--r--spec/models/label_spec.rb30
-rw-r--r--spec/models/merge_request_spec.rb86
-rw-r--r--spec/models/milestone_spec.rb3
-rw-r--r--spec/models/note_spec.rb16
-rw-r--r--spec/models/project_spec.rb81
-rw-r--r--spec/models/project_team_spec.rb26
-rw-r--r--spec/models/repository_spec.rb239
-rw-r--r--spec/models/todo_spec.rb69
-rw-r--r--spec/models/user_spec.rb29
-rw-r--r--spec/requests/api/branches_spec.rb4
-rw-r--r--spec/requests/api/builds_spec.rb166
-rw-r--r--spec/requests/api/commit_status_spec.rb174
-rw-r--r--spec/requests/api/commits_spec.rb4
-rw-r--r--spec/requests/api/fork_spec.rb2
-rw-r--r--spec/requests/api/internal_spec.rb12
-rw-r--r--spec/requests/api/project_members_spec.rb4
-rw-r--r--spec/requests/api/projects_spec.rb4
-rw-r--r--spec/requests/api/repositories_spec.rb17
-rw-r--r--spec/requests/api/runners_spec.rb464
-rw-r--r--spec/requests/api/tags_spec.rb4
-rw-r--r--spec/requests/api/triggers_spec.rb4
-rw-r--r--spec/requests/api/variables_spec.rb4
-rw-r--r--spec/requests/ci/api/builds_spec.rb44
-rw-r--r--spec/routing/routing_spec.rb5
-rw-r--r--spec/services/archive_repository_service_spec.rb25
-rw-r--r--spec/services/ci/create_builds_service_spec.rb28
-rw-r--r--spec/services/git_push_service_spec.rb73
-rw-r--r--spec/services/issues/close_service_spec.rb6
-rw-r--r--spec/services/issues/create_service_spec.rb23
-rw-r--r--spec/services/issues/update_service_spec.rb69
-rw-r--r--spec/services/merge_requests/close_service_spec.rb5
-rw-r--r--spec/services/merge_requests/create_service_spec.rb42
-rw-r--r--spec/services/merge_requests/merge_when_build_succeeds_service_spec.rb82
-rw-r--r--spec/services/merge_requests/refresh_service_spec.rb2
-rw-r--r--spec/services/merge_requests/update_service_spec.rb79
-rw-r--r--spec/services/notes/post_process_service_spec.rb1
-rw-r--r--spec/services/notes/update_service_spec.rb45
-rw-r--r--spec/services/system_note_service_spec.rb19
-rw-r--r--spec/services/todo_service_spec.rb274
-rw-r--r--spec/support/capybara.rb2
-rw-r--r--spec/support/project_hook_data_shared_example.rb27
-rw-r--r--spec/support/workhorse_helpers.rb16
-rw-r--r--spec/views/devise/shared/_signin_box.html.haml_spec.rb37
-rw-r--r--spec/workers/repository_fork_worker_spec.rb12
-rw-r--r--spec/workers/repository_import_worker_spec.rb19
-rwxr-xr-xvendor/assets/javascripts/cropper.js2972
-rwxr-xr-xvendor/assets/stylesheets/cropper.css379
680 files changed, 36119 insertions, 7225 deletions
diff --git a/.gitignore b/.gitignore
index 91ea81bfc4e..1eb785451f4 100644
--- a/.gitignore
+++ b/.gitignore
@@ -23,6 +23,7 @@ config/gitlab.yml
config/gitlab_ci.yml
config/initializers/rack_attack.rb
config/initializers/smtp_settings.rb
+config/initializers/relative_url.rb
config/resque.yml
config/unicorn.rb
config/secrets.yml
diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index 1c4d98ea3f6..c477721f9da 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -1,4 +1,4 @@
-image: "ruby:2.2"
+image: "ruby:2.1"
services:
- mysql:latest
@@ -6,7 +6,7 @@ services:
- redis:latest
cache:
- key: "ruby22"
+ key: "ruby21"
paths:
- vendor
@@ -24,7 +24,12 @@ before_script:
- bundle install --without postgres production --jobs $(nproc) "${FLAGS[@]}"
- RAILS_ENV=test bundle exec rake db:drop db:create db:schema:load db:migrate
+stages:
+- test
+- notifications
+
spec:feature:
+ stage: test
script:
- RAILS_ENV=test bundle exec rake assets:precompile 2>/dev/null
- RAILS_ENV=test SIMPLECOV=true bundle exec rake spec:feature
@@ -33,6 +38,7 @@ spec:feature:
- mysql
spec:api:
+ stage: test
script:
- RAILS_ENV=test SIMPLECOV=true bundle exec rake spec:api
tags:
@@ -40,6 +46,7 @@ spec:api:
- mysql
spec:models:
+ stage: test
script:
- RAILS_ENV=test SIMPLECOV=true bundle exec rake spec:models
tags:
@@ -47,6 +54,7 @@ spec:models:
- mysql
spec:lib:
+ stage: test
script:
- RAILS_ENV=test SIMPLECOV=true bundle exec rake spec:lib
tags:
@@ -54,6 +62,7 @@ spec:lib:
- mysql
spec:services:
+ stage: test
script:
- RAILS_ENV=test SIMPLECOV=true bundle exec rake spec:services
tags:
@@ -61,6 +70,7 @@ spec:services:
- mysql
spec:benchmark:
+ stage: test
script:
- RAILS_ENV=test bundle exec rake spec:benchmark
tags:
@@ -69,6 +79,7 @@ spec:benchmark:
allow_failure: true
spec:other:
+ stage: test
script:
- RAILS_ENV=test SIMPLECOV=true bundle exec rake spec:other
tags:
@@ -76,27 +87,34 @@ spec:other:
- mysql
spinach:project:half:
+ stage: test
script:
+ - RAILS_ENV=test bundle exec rake assets:precompile 2>/dev/null
- RAILS_ENV=test SIMPLECOV=true bundle exec rake spinach:project:half
tags:
- ruby
- mysql
spinach:project:rest:
+ stage: test
script:
+ - RAILS_ENV=test bundle exec rake assets:precompile 2>/dev/null
- RAILS_ENV=test SIMPLECOV=true bundle exec rake spinach:project:rest
tags:
- ruby
- mysql
spinach:other:
+ stage: test
script:
+ - RAILS_ENV=test bundle exec rake assets:precompile 2>/dev/null
- RAILS_ENV=test SIMPLECOV=true bundle exec rake spinach:other
tags:
- ruby
- mysql
teaspoon:
+ stage: test
script:
- RAILS_ENV=test bundle exec teaspoon
tags:
@@ -104,6 +122,7 @@ teaspoon:
- mysql
rubocop:
+ stage: test
script:
- bundle exec rubocop
tags:
@@ -111,6 +130,7 @@ rubocop:
- mysql
brakeman:
+ stage: test
script:
- bundle exec rake brakeman
tags:
@@ -118,6 +138,7 @@ brakeman:
- mysql
flog:
+ stage: test
script:
- bundle exec rake flog
tags:
@@ -125,6 +146,7 @@ flog:
- mysql
flay:
+ stage: test
script:
- bundle exec rake flay
tags:
@@ -132,6 +154,7 @@ flay:
- mysql
bundler:audit:
+ stage: test
script:
- "bundle exec bundle-audit update"
- "bundle exec bundle-audit check"
@@ -140,87 +163,93 @@ bundler:audit:
- mysql
allow_failure: true
-# Ruby 2.1 jobs
+# Ruby 2.2 jobs
-spec:feature:ruby21:
- image: ruby:2.1
+spec:feature:ruby22:
+ stage: test
+ image: ruby:2.2
only:
- master
script:
- RAILS_ENV=test bundle exec rake assets:precompile 2>/dev/null
- RAILS_ENV=test SIMPLECOV=true bundle exec rake spec:feature
cache:
- key: "ruby21"
+ key: "ruby22"
paths:
- vendor
tags:
- ruby
- mysql
-spec:api:ruby21:
- image: ruby:2.1
+spec:api:ruby22:
+ stage: test
+ image: ruby:2.2
only:
- master
script:
- RAILS_ENV=test SIMPLECOV=true bundle exec rake spec:api
cache:
- key: "ruby21"
+ key: "ruby22"
paths:
- vendor
tags:
- ruby
- mysql
-spec:models:ruby21:
- image: ruby:2.1
+spec:models:ruby22:
+ stage: test
+ image: ruby:2.2
only:
- master
script:
- RAILS_ENV=test SIMPLECOV=true bundle exec rake spec:models
cache:
- key: "ruby21"
+ key: "ruby22"
paths:
- vendor
tags:
- ruby
- mysql
-spec:lib:ruby21:
- image: ruby:2.1
+spec:lib:ruby22:
+ stage: test
+ image: ruby:2.2
only:
- master
script:
- RAILS_ENV=test SIMPLECOV=true bundle exec rake spec:lib
cache:
- key: "ruby21"
+ key: "ruby22"
paths:
- vendor
tags:
- ruby
- mysql
-spec:services:ruby21:
- image: ruby:2.1
+spec:services:ruby22:
+ stage: test
+ image: ruby:2.2
only:
- master
script:
- RAILS_ENV=test SIMPLECOV=true bundle exec rake spec:services
cache:
- key: "ruby21"
+ key: "ruby22"
paths:
- vendor
tags:
- ruby
- mysql
-spec:benchmark:ruby21:
- image: ruby:2.1
+spec:benchmark:ruby22:
+ stage: test
+ image: ruby:2.2
only:
- master
script:
- RAILS_ENV=test bundle exec rake spec:benchmark
cache:
- key: "ruby21"
+ key: "ruby22"
paths:
- vendor
tags:
@@ -228,59 +257,77 @@ spec:benchmark:ruby21:
- mysql
allow_failure: true
-spec:other:ruby21:
- image: ruby:2.1
+spec:other:ruby22:
+ stage: test
+ image: ruby:2.2
only:
- master
script:
- RAILS_ENV=test SIMPLECOV=true bundle exec rake spec:other
cache:
- key: "ruby21"
+ key: "ruby22"
paths:
- vendor
tags:
- ruby
- mysql
-spinach:project:half:ruby21:
- image: ruby:2.1
+spinach:project:half:ruby22:
+ stage: test
+ image: ruby:2.2
only:
- master
script:
+ - RAILS_ENV=test bundle exec rake assets:precompile 2>/dev/null
- RAILS_ENV=test SIMPLECOV=true bundle exec rake spinach:project:half
cache:
- key: "ruby21"
+ key: "ruby22"
paths:
- vendor
tags:
- ruby
- mysql
-spinach:project:rest:ruby21:
- image: ruby:2.1
+spinach:project:rest:ruby22:
+ stage: test
+ image: ruby:2.2
only:
- master
script:
+ - RAILS_ENV=test bundle exec rake assets:precompile 2>/dev/null
- RAILS_ENV=test SIMPLECOV=true bundle exec rake spinach:project:rest
cache:
- key: "ruby21"
+ key: "ruby22"
paths:
- vendor
tags:
- ruby
- mysql
-spinach:other:ruby21:
- image: ruby:2.1
+spinach:other:ruby22:
+ stage: test
+ image: ruby:2.2
only:
- master
script:
+ - RAILS_ENV=test bundle exec rake assets:precompile 2>/dev/null
- RAILS_ENV=test SIMPLECOV=true bundle exec rake spinach:other
cache:
- key: "ruby21"
+ key: "ruby22"
paths:
- vendor
tags:
- ruby
- mysql
+
+notify:slack:
+ stage: notifications
+ script:
+ - ./scripts/notify_slack.sh "#builds" "Build on \`$CI_BUILD_REF_NAME\` failed! Commit \`$(git log -1 --oneline)\` See <https://gitlab.com/gitlab-org/$(basename "$PWD")/commit/"$CI_BUILD_REF"/builds>"
+ when: on_failure
+ only:
+ - master@gitlab-org/gitlab-ce
+ - tags@gitlab-org/gitlab-ce
+ - master@gitlab-org/gitlab-ee
+ - tags@gitlab-org/gitlab-ee \ No newline at end of file
diff --git a/.ruby-version b/.ruby-version
index 530cdd91a20..ebf14b46981 100644
--- a/.ruby-version
+++ b/.ruby-version
@@ -1 +1 @@
-2.2.4
+2.1.8
diff --git a/CHANGELOG b/CHANGELOG
index b28e1f7a9fd..a9742d22015 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -1,6 +1,72 @@
Please view this file on the master branch, on stable branches it's out of date.
-v 8.5.0 (unreleased)
+v 8.6.0 (unreleased)
+ - Contributions to forked projects are included in calendar
+ - Improve the formatting for the user page bio (Connor Shea)
+ - Removed the default password from the initial admin account created during
+ setup. A password can be provided during setup (see installation docs), or
+ GitLab will ask the user to create a new one upon first visit.
+ - Fix issue when pushing to projects ending in .wiki
+ - Fix avatar stretching by providing a cropping feature (Johann Pardanaud)
+ - Don't load all of GitLab in mail_room
+ - Indicate how much an MR diverged from the target branch (Pierre de La Morinerie)
+ - Strip leading and trailing spaces in URL validator (evuez)
+ - Return empty array instead of 404 when commit has no statuses in commit status API
+ - Add support for cross-project label references
+ - Update documentation to reflect Guest role not being enforced on internal projects
+ - Allow search for logged out users
+ - Don't show Issues/MRs from archived projects in Groups view
+ - Increase the notes polling timeout over time (Roberto Dip)
+ - Show labels in dashboard and group milestone views
+
+v 8.5.4
+ - Do not cache requests for badges (including builds badge)
+
+v 8.5.3
+ - Flush repository caches before renaming projects
+ - Sort starred projects on dashboard based on last activity by default
+ - Show commit message in JIRA mention comment
+ - Makes issue page and merge request page usable on mobile browsers.
+
+v 8.5.2
+ - Fix sidebar overlapping content when screen width was below 1200px
+ - Don't repeat labels listed on Labels tab
+ - Bring the "branded appearance" feature from EE to CE
+ - Fix error 500 when commenting on a commit
+ - Show days remaining instead of elapsed time for Milestone
+ - Fix broken icons on installations with relative URL (Artem Sidorenko)
+ - Fix issue where tag list wasn't refreshed after deleting a tag
+ - Fix import from gitlab.com (KazSawada)
+ - Improve implementation to check read access to forks and add pagination
+ - Don't show any "2FA required" message if it's not actually required
+ - Fix help keyboard shortcut on relative URL setups (Artem Sidorenko)
+ - Update Rails to 4.2.5.2
+ - Fix permissions for deprecated CI build status badge
+ - Don't show "Welcome to GitLab" when the search didn't return any projects
+ - Add Todos documentation
+
+v 8.5.1
+ - Fix group projects styles
+ - Show Crowd login tab when sign in is disabled and Crowd is enabled (Peter Hudec)
+ - Fix a set of small UI glitches in project, profile, and wiki pages
+ - Restrict permissions on public/uploads
+ - Fix the merge request side-by-side view after loading diff results
+ - Fix the look of tooltip for the "Revert" button
+ - Add when the Builds & Runners API changes got introduced
+ - Fix error 500 on some merged merge requests
+ - Fix an issue causing the content of the issuable sidebar to disappear
+ - Fix error 500 when trying to mark an already done todo as "done"
+ - Fix an issue where MRs weren't sortable
+ - Issues can now be dragged & dropped into empty milestone lists. This is also
+ possible with MRs
+ - Changed padding & background color for highlighted notes
+ - Re-add the newrelic_rpm gem which was removed without any deprecation or warning (Stan Hu)
+ - Update sentry-raven gem to 0.15.6
+ - Add build coverage in project's builds page (Steffen Köhler)
+ - Changed # to ! for merge requests in activity view
+
+v 8.5.0
+ - Fix duplicate "me" in tooltip of the "thumbsup" awards Emoji (Stan Hu)
- Cache various Repository methods to improve performance (Yorick Peterse)
- Fix duplicated branch creation/deletion Web hooks/service notifications when using Web UI (Stan Hu)
- Ensure rake tasks that don't need a DB connection can be run without one
@@ -33,6 +99,11 @@ v 8.5.0 (unreleased)
- Update the ExternalIssue regex pattern (Blake Hitchcock)
- Remember user's inline/side-by-side diff view preference in a cookie (Kirill Katsnelson)
- Optimized performance of finding issues to be closed by a merge request
+ - Add `avatar_url`, `description`, `git_ssh_url`, `git_http_url`, `path_with_namespace`
+ and `default_branch` in `project` in push, issue, merge-request and note webhooks data (Kirill Zaitsev)
+ - Deprecate the `ssh_url` in favor of `git_ssh_url` and `http_url` in favor of `git_http_url`
+ in `project` for push, issue, merge-request and note webhooks data (Kirill Zaitsev)
+ - Deprecate the `repository` key in push, issue, merge-request and note webhooks data, use `project` instead (Kirill Zaitsev)
- API: Expose MergeRequest#merge_status (Andrei Dziahel)
- Revert "Add IP check against DNSBLs at account sign-up"
- Actually use the `skip_merges` option in Repository#commits (Tony Chu)
@@ -40,21 +111,41 @@ v 8.5.0 (unreleased)
- Deprecate API "merge_request/:merge_request_id/comments". Use "merge_requests/:merge_request_id/notes" instead
- Deprecate API "merge_request/:merge_request_id/...". Use "merge_requests/:merge_request_id/..." instead
- Prevent parse error when name of project ends with .atom and prevent path issues
+ - Discover branches for commit statuses ref-less when doing merge when succeeded
- Mark inline difference between old and new paths when a file is renamed
- Support Akismet spam checking for creation of issues via API (Stan Hu)
- API: Allow to set or update a merge-request's milestone (Kirill Skachkov)
- Improve UI consistency between projects and groups lists
- Add sort dropdown to dashboard projects page
- Fixed logo animation on Safari (Roman Rott)
+ - Fix Merge When Succeeded when multiple stages
- Hide remove source branch button when the MR is merged but new commits are pushed (Zeger-Jan van de Weg)
- In seach autocomplete show only groups and projects you are member of
+ - Don't process cross-reference notes from forks
- Fix: init.d script not working on OS X
- Faster snippet search
+ - Added API to download build artifacts
- Title for milestones should be unique (Zeger-Jan van de Weg)
- Validate correctness of maximum attachment size application setting
- Replaces "Create merge request" link with one to the "Merge Request" when one exists
- Fix CI builds badge, add a new link to builds badge, deprecate the old one
- Fix broken link to project in build notification emails
+ - Ability to see and sort on vote count from Issues and MR lists
+ - Fix builds scheduler when first build in stage was allowed to fail
+ - User project limit is reached notice is hidden if the projects limit is zero
+ - Add API support for managing runners and project's runners
+ - Allow SAML users to login with no previous account without having to allow
+ all Omniauth providers to do so.
+ - Allow existing users to auto link their SAML credentials by logging in via SAML
+ - Make it possible to erase a build (trace, artifacts) using UI and API
+ - Ability to revert changes from a Merge Request or Commit
+ - Emoji comment on diffs are not award emoji
+ - Add label description (Nuttanart Pornprasitsakul)
+ - Show label row when filtering issues or merge requests by label (Nuttanart Pornprasitsakul)
+ - Add Todos
+
+v 8.4.5
+ - No CE-specific changes
v 8.4.4
- Update omniauth-saml gem to 1.4.2
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index c4522998f42..30c97429040 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -3,24 +3,27 @@
**Table of Contents** *generated with [DocToc](https://github.com/thlorenz/doctoc)*
- [Contribute to GitLab](#contribute-to-gitlab)
- - [Contributor license agreement](#contributor-license-agreement)
- - [Security vulnerability disclosure](#security-vulnerability-disclosure)
- - [Closing policy for issues and merge requests](#closing-policy-for-issues-and-merge-requests)
- - [Helping others](#helping-others)
- - [I want to contribute!](#i-want-to-contribute)
- - [Issue tracker](#issue-tracker)
- - [Feature proposals](#feature-proposals)
- - [Issue tracker guidelines](#issue-tracker-guidelines)
- - [Issue weight](#issue-weight)
- - [Regression issues](#regression-issues)
- - [Merge requests](#merge-requests)
- - [Merge request guidelines](#merge-request-guidelines)
- - [Merge request description format](#merge-request-description-format)
- - [Contribution acceptance criteria](#contribution-acceptance-criteria)
- - [Changes for Stable Releases](#changes-for-stable-releases)
- - [Definition of done](#definition-of-done)
- - [Style guides](#style-guides)
- - [Code of conduct](#code-of-conduct)
+ - [Contributor license agreement](#contributor-license-agreement)
+ - [Security vulnerability disclosure](#security-vulnerability-disclosure)
+ - [Closing policy for issues and merge requests](#closing-policy-for-issues-and-merge-requests)
+ - [Helping others](#helping-others)
+ - [I want to contribute!](#i-want-to-contribute)
+ - [Implement design & UI elements](#implement-design-ui-elements)
+ - [Design reference](#design-reference)
+ - [UI development kit](#ui-development-kit)
+ - [Issue tracker](#issue-tracker)
+ - [Feature proposals](#feature-proposals)
+ - [Issue tracker guidelines](#issue-tracker-guidelines)
+ - [Issue weight](#issue-weight)
+ - [Regression issues](#regression-issues)
+ - [Merge requests](#merge-requests)
+ - [Merge request guidelines](#merge-request-guidelines)
+ - [Merge request description format](#merge-request-description-format)
+ - [Contribution acceptance criteria](#contribution-acceptance-criteria)
+ - [Changes for Stable Releases](#changes-for-stable-releases)
+ - [Definition of done](#definition-of-done)
+ - [Style guides](#style-guides)
+ - [Code of conduct](#code-of-conduct)
<!-- END doctoc generated TOC please keep comment here to allow auto update -->
@@ -34,7 +37,7 @@ source edition, and GitLab Enterprise Edition (EE) which is our commercial
edition. Throughout this guide you will see references to CE and EE for
abbreviation.
-If you have read this guide and want to know how the GitLab [core-team][]
+If you have read this guide and want to know how the GitLab [core team][core-team]
operates please see [the GitLab contributing process](PROCESS.md).
## Contributor license agreement
@@ -68,10 +71,10 @@ for audiences of all ages.
## Helping others
Please help other GitLab users when you can. The channels people will reach out
-on can be found on the [getting help page][].
+on can be found on the [getting help page][getting-help].
Sign up for the mailing list, answer GitLab questions on StackOverflow or
-respond in the IRC channel. You can also sign up on [CodeTriage][] to help with
+respond in the IRC channel. You can also sign up on [CodeTriage][codetriage] to help with
the remaining issues on the GitHub issue tracker.
## I want to contribute!
@@ -83,6 +86,22 @@ GitLab.
This was inspired by [an article by Kent C. Dodds][medium-up-for-grabs].
+## Implement design & UI elements
+
+### Design reference
+
+The GitLab design reference can be found in the [gitlab-design] project.
+The designs are made using Antetype (`.atype` files). You can use the
+[free Antetype viewer (Mac OSX only)] or grab an exported PNG from the design
+(the PNG is 1:1).
+
+The current designs can be found in the [`gitlab1.atype` file].
+
+### UI development kit
+
+Implemented UI elements can also be found at https://gitlab.com/help/ui. Please
+note that this page isn't comprehensive at this time.
+
## Issue tracker
To get support for your particular problem please use the
@@ -115,7 +134,7 @@ For feature proposals for EE, open an issue on the
In order to help track the feature proposals, we have created a
[`feature proposal`][fpl] label. For the time being, users that are not members
-of the project cannot add labels. You can instead ask one of the [core team][]
+of the project cannot add labels. You can instead ask one of the [core team][core-team]
members to add the label `feature proposal` to the issue.
Please keep feature proposals as small and simple as possible, complex ones
@@ -299,13 +318,14 @@ to us than having a minimal commit log. The smaller an MR is the more likely it
is it will be merged (quickly). After that you can send more MRs to enhance it.
For examples of feedback on merge requests please look at already
-[closed merge requests][]. If you would like quick feedback on your merge
-request feel free to mention one of the Merge Marshalls of the [core team][].
+[closed merge requests][closed-merge-requests]. If you would like quick feedback
+on your merge request feel free to mention one of the Merge Marshalls in the
+[core team][core-team] or one of the
+[Merge request coaches](https://about.gitlab.com/team/).
Please ensure that your merge request meets the contribution acceptance criteria.
When having your code reviewed and when reviewing merge requests please take the
-[thoughtbot code review guidelines](https://github.com/thoughtbot/guides/tree/master/code-review)
-into account.
+[Thoughtbot code review guide] into account.
### Merge request description format
@@ -343,7 +363,8 @@ description area. Copy-paste it to retain the markdown format.
to a new table or remove an old table) to aid retrying on failure
1. Keeps the GitLab code base clean and well structured
1. Contains functionality we think other users will benefit from too
-1. Doesn't add configuration options since they complicate future changes
+1. Doesn't add configuration options or settings options since they complicate
+ making and testing future changes
1. Changes after submitting the merge request should be in separate commits
(no squashing). If necessary, you will be asked to squash when the review is
over, before merging.
@@ -369,7 +390,7 @@ Like all merge requests the target should be master so all bugfixes are in maste
## Definition of done
If you contribute to GitLab please know that changes involve more than just
-code. We have the following [definition of done][]. Please ensure you support
+code. We have the following [definition of done][definition-of-done]. Please ensure you support
the feature you contribute through all of these steps.
1. Description explaining the relevancy (see following item)
@@ -448,12 +469,12 @@ when an individual is representing the project or its community.
Instances of abusive, harassing, or otherwise unacceptable behavior can be
reported by emailing `contact@gitlab.com`.
-This Code of Conduct is adapted from the [Contributor Covenant][], version 1.1.0,
+This Code of Conduct is adapted from the [Contributor Covenant][contributor-covenant], version 1.1.0,
available at [http://contributor-covenant.org/version/1/1/0/](http://contributor-covenant.org/version/1/1/0/).
[core-team]: https://about.gitlab.com/core-team/
-[getting help page]: https://about.gitlab.com/getting-help/
-[Codetriage]: http://www.codetriage.com/gitlabhq/gitlabhq
+[getting-help]: https://about.gitlab.com/getting-help/
+[codetriage]: http://www.codetriage.com/gitlabhq/gitlabhq
[up-for-grabs]: https://gitlab.com/gitlab-org/gitlab-ce/issues?label_name=up-for-grabs
[medium-up-for-grabs]: https://medium.com/@kentcdodds/first-timers-only-78281ea47455
[ce-tracker]: https://gitlab.com/gitlab-org/gitlab-ce/issues
@@ -467,9 +488,13 @@ available at [http://contributor-covenant.org/version/1/1/0/](http://contributor
[github-mr-tracker]: https://github.com/gitlabhq/gitlabhq/pulls
[gdk]: https://gitlab.com/gitlab-org/gitlab-development-kit
[git-squash]: https://git-scm.com/book/en/Git-Tools-Rewriting-History#Squashing-Commits
-[closed merge requests]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests?assignee_id=&label_name=&milestone_id=&scope=&sort=&state=closed
-[definition of done]: http://guide.agilealliance.org/guide/definition-of-done.html
-[Contributor Covenant]: http://contributor-covenant.org
+[closed-merge-requests]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests?assignee_id=&label_name=&milestone_id=&scope=&sort=&state=closed
+[definition-of-done]: http://guide.agilealliance.org/guide/definition-of-done.html
+[contributor-covenant]: http://contributor-covenant.org
[rss-source]: https://github.com/bbatsov/ruby-style-guide/blob/master/README.md#source-code-layout
[rss-naming]: https://github.com/bbatsov/ruby-style-guide/blob/master/README.md#naming
[doc-styleguide]: doc/development/doc_styleguide.md "Documentation styleguide"
+[gitlab-design]: https://gitlab.com/gitlab-org/gitlab-design
+[free Antetype viewer (Mac OSX only)]: https://itunes.apple.com/us/app/antetype-viewer/id824152298?mt=12
+[`gitlab1.atype` file]: https://gitlab.com/gitlab-org/gitlab-design/tree/master/gitlab1.atype/
+[Thoughtbot code review guide]: https://github.com/thoughtbot/guides/tree/master/code-review
diff --git a/GITLAB_WORKHORSE_VERSION b/GITLAB_WORKHORSE_VERSION
index d2b13eb644d..ef5e4454454 100644
--- a/GITLAB_WORKHORSE_VERSION
+++ b/GITLAB_WORKHORSE_VERSION
@@ -1 +1 @@
-0.6.4
+0.6.5
diff --git a/Gemfile b/Gemfile
index a1c57af4bac..c66ef3cffad 100644
--- a/Gemfile
+++ b/Gemfile
@@ -1,14 +1,14 @@
source "https://rubygems.org"
-gem 'rails', '4.2.5.1'
+gem 'rails', '4.2.5.2'
gem 'rails-deprecated_sanitizer', '~> 1.0.3'
# Responders respond_to and respond_with
gem 'responders', '~> 2.0'
-# Specify a sprockets version due to security issue
-# See https://groups.google.com/forum/#!topic/rubyonrails-security/doAVp0YaTqY
-gem 'sprockets', '~> 2.12.3'
+# Specify a sprockets version due to increased performance
+# See https://gitlab.com/gitlab-org/gitlab-ce/issues/6069
+gem 'sprockets', '~> 3.3.5'
# Default values for AR models
gem "default_value_for", "~> 3.0.0"
@@ -50,7 +50,7 @@ gem "browser", '~> 1.0.0'
# Extracting information from a git repository
# Provide access to Gitlab::Git library
-gem "gitlab_git", '~> 8.1'
+gem "gitlab_git", '~> 9.0'
# LDAP Auth
# GitLab fork with several improvements to original library. For full list of changes
@@ -75,7 +75,10 @@ gem "kaminari", "~> 0.16.3"
gem "haml-rails", '~> 0.9.0'
# Files attachments
-gem "carrierwave", '~> 0.9.0'
+gem "carrierwave", '~> 0.10.0'
+
+# Image editing
+gem "mini_magick", '~> 4.4.0'
# Drag and Drop UI
gem 'dropzonejs-rails', '~> 0.7.1'
@@ -112,7 +115,7 @@ gem 'diffy', '~> 3.0.3'
# Application server
group :unicorn do
- gem "unicorn", '~> 4.8.2'
+ gem "unicorn", '~> 4.9.0'
gem 'unicorn-worker-killer', '~> 0.4.2'
end
@@ -204,13 +207,12 @@ gem 'jquery-turbolinks', '~> 2.1.0'
gem 'addressable', '~> 2.3.8'
gem 'bootstrap-sass', '~> 3.3.0'
gem 'font-awesome-rails', '~> 4.2'
-gem 'gitlab_emoji', '~> 0.2.0'
+gem 'gitlab_emoji', '~> 0.3.0'
gem 'gon', '~> 6.0.1'
gem 'jquery-atwho-rails', '~> 1.3.2'
gem 'jquery-rails', '~> 4.0.0'
gem 'jquery-scrollto-rails', '~> 1.4.3'
gem 'jquery-ui-rails', '~> 5.0.0'
-gem 'nprogress-rails', '~> 0.1.6.7'
gem 'raphael-rails', '~> 2.1.2'
gem 'request_store', '~> 1.2.0'
gem 'select2-rails', '~> 3.5.9'
@@ -218,7 +220,7 @@ gem 'virtus', '~> 1.0.1'
gem 'net-ssh', '~> 3.0.1'
# Sentry integration
-gem 'sentry-raven'
+gem 'sentry-raven', '~> 0.15'
# Metrics
group :metrics do
@@ -258,10 +260,10 @@ group :development, :test do
gem 'awesome_print', '~> 1.2.0', require: false
gem 'fuubar', '~> 2.0.0'
- gem 'database_cleaner', '~> 1.4.0'
- gem 'factory_girl_rails', '~> 4.3.0'
- gem 'rspec-rails', '~> 3.3.0'
- gem 'spinach-rails', '~> 0.2.1'
+ gem 'database_cleaner', '~> 1.4.0'
+ gem 'factory_girl_rails', '~> 4.6.0'
+ gem 'rspec-rails', '~> 3.3.0'
+ gem 'spinach-rails', '~> 0.2.1'
# Prevent occasions where minitest is not bundled in packaged versions of ruby (see #3826)
gem 'minitest', '~> 5.7.0'
@@ -276,7 +278,7 @@ group :development, :test do
gem 'teaspoon', '~> 1.0.0'
gem 'teaspoon-jasmine', '~> 2.2.0'
- gem 'spring', '~> 1.3.6'
+ gem 'spring', '~> 1.6.4'
gem 'spring-commands-rspec', '~> 1.0.4'
gem 'spring-commands-spinach', '~> 1.0.0'
gem 'spring-commands-teaspoon', '~> 0.0.2'
@@ -303,6 +305,8 @@ group :production do
gem "gitlab_meta", '7.0'
end
+gem "newrelic_rpm", '~> 3.14'
+
gem 'octokit', '~> 3.8.0'
gem "mail_room", "~> 0.6.1"
diff --git a/Gemfile.lock b/Gemfile.lock
index 718285e1665..22c86e4ae8f 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -4,41 +4,41 @@ GEM
CFPropertyList (2.3.2)
RedCloth (4.2.9)
ace-rails-ap (2.0.1)
- actionmailer (4.2.5.1)
- actionpack (= 4.2.5.1)
- actionview (= 4.2.5.1)
- activejob (= 4.2.5.1)
+ actionmailer (4.2.5.2)
+ actionpack (= 4.2.5.2)
+ actionview (= 4.2.5.2)
+ activejob (= 4.2.5.2)
mail (~> 2.5, >= 2.5.4)
rails-dom-testing (~> 1.0, >= 1.0.5)
- actionpack (4.2.5.1)
- actionview (= 4.2.5.1)
- activesupport (= 4.2.5.1)
+ actionpack (4.2.5.2)
+ actionview (= 4.2.5.2)
+ activesupport (= 4.2.5.2)
rack (~> 1.6)
rack-test (~> 0.6.2)
rails-dom-testing (~> 1.0, >= 1.0.5)
rails-html-sanitizer (~> 1.0, >= 1.0.2)
- actionview (4.2.5.1)
- activesupport (= 4.2.5.1)
+ actionview (4.2.5.2)
+ activesupport (= 4.2.5.2)
builder (~> 3.1)
erubis (~> 2.7.0)
rails-dom-testing (~> 1.0, >= 1.0.5)
rails-html-sanitizer (~> 1.0, >= 1.0.2)
- activejob (4.2.5.1)
- activesupport (= 4.2.5.1)
+ activejob (4.2.5.2)
+ activesupport (= 4.2.5.2)
globalid (>= 0.3.0)
- activemodel (4.2.5.1)
- activesupport (= 4.2.5.1)
+ activemodel (4.2.5.2)
+ activesupport (= 4.2.5.2)
builder (~> 3.1)
- activerecord (4.2.5.1)
- activemodel (= 4.2.5.1)
- activesupport (= 4.2.5.1)
+ activerecord (4.2.5.2)
+ activemodel (= 4.2.5.2)
+ activesupport (= 4.2.5.2)
arel (~> 6.0)
activerecord-deprecated_finders (1.0.4)
activerecord-session_store (0.1.2)
actionpack (>= 4.0.0, < 5)
activerecord (>= 4.0.0, < 5)
railties (>= 4.0.0, < 5)
- activesupport (4.2.5.1)
+ activesupport (4.2.5.2)
i18n (~> 0.7)
json (~> 1.7, >= 1.7.7)
minitest (~> 5.1)
@@ -50,7 +50,7 @@ GEM
after_commit_queue (1.3.0)
activerecord (>= 3.0)
akismet (2.0.0)
- allocations (1.0.3)
+ allocations (1.0.4)
annotate (2.6.10)
activerecord (>= 3.2, <= 4.3)
rake (~> 10.4)
@@ -117,10 +117,11 @@ GEM
capybara-screenshot (1.0.11)
capybara (>= 1.0, < 3)
launchy
- carrierwave (0.9.0)
+ carrierwave (0.10.0)
activemodel (>= 3.2.0)
activesupport (>= 3.2.0)
json (>= 1.7)
+ mime-types (>= 1.16)
cause (0.1)
charlock_holmes (0.7.3)
chunky_png (1.3.5)
@@ -194,10 +195,10 @@ GEM
excon (0.45.4)
execjs (2.6.0)
expression_parser (0.9.0)
- factory_girl (4.3.0)
+ factory_girl (4.5.0)
activesupport (>= 3.0.0)
- factory_girl_rails (4.3.0)
- factory_girl (~> 4.3.0)
+ factory_girl_rails (4.6.0)
+ factory_girl (~> 4.5.0)
railties (>= 3.0.0)
faraday (0.9.2)
multipart-post (>= 1.2, < 3)
@@ -336,11 +337,11 @@ GEM
ruby-progressbar (~> 1.4)
gemnasium-gitlab-service (0.2.6)
rugged (~> 0.21)
- gemojione (2.1.1)
+ gemojione (2.2.1)
json
get_process_mem (0.2.0)
gherkin-ruby (0.3.2)
- github-linguist (4.7.3)
+ github-linguist (4.7.5)
charlock_holmes (~> 0.7.3)
escape_utils (~> 1.1.0)
mime-types (>= 1.19)
@@ -355,13 +356,13 @@ GEM
diff-lcs (~> 1.1)
mime-types (~> 1.15)
posix-spawn (~> 0.3)
- gitlab_emoji (0.2.0)
- gemojione (~> 2.1)
- gitlab_git (8.1.0)
+ gitlab_emoji (0.3.1)
+ gemojione (~> 2.2, >= 2.2.1)
+ gitlab_git (9.0.0)
activesupport (~> 4.0)
charlock_holmes (~> 0.7.3)
github-linguist (~> 4.7.0)
- rugged (~> 0.23.3)
+ rugged (~> 0.24.0b13)
gitlab_meta (7.0)
gitlab_omniauth-ldap (1.2.1)
net-ldap (~> 0.9)
@@ -407,7 +408,6 @@ GEM
railties (>= 4.0.1)
hashie (3.4.3)
highline (1.7.8)
- hike (1.2.3)
hipchat (1.5.2)
httparty
mimemagic
@@ -468,6 +468,7 @@ GEM
method_source (0.8.2)
mime-types (1.25.1)
mimemagic (0.3.0)
+ mini_magick (4.4.0)
mini_portile2 (2.0.0)
minitest (5.7.0)
mousetrap-rails (1.4.6)
@@ -479,9 +480,9 @@ GEM
net-ldap (0.12.1)
net-ssh (3.0.1)
netrc (0.11.0)
+ newrelic_rpm (3.14.1.311)
nokogiri (1.6.7.2)
mini_portile2 (~> 2.0.0.rc2)
- nprogress-rails (0.1.6.7)
oauth (0.4.7)
oauth2 (1.0.0)
faraday (>= 0.8, < 0.10)
@@ -585,16 +586,16 @@ GEM
rack
rack-test (0.6.3)
rack (>= 1.0)
- rails (4.2.5.1)
- actionmailer (= 4.2.5.1)
- actionpack (= 4.2.5.1)
- actionview (= 4.2.5.1)
- activejob (= 4.2.5.1)
- activemodel (= 4.2.5.1)
- activerecord (= 4.2.5.1)
- activesupport (= 4.2.5.1)
+ rails (4.2.5.2)
+ actionmailer (= 4.2.5.2)
+ actionpack (= 4.2.5.2)
+ actionview (= 4.2.5.2)
+ activejob (= 4.2.5.2)
+ activemodel (= 4.2.5.2)
+ activerecord (= 4.2.5.2)
+ activesupport (= 4.2.5.2)
bundler (>= 1.3.0, < 2.0)
- railties (= 4.2.5.1)
+ railties (= 4.2.5.2)
sprockets-rails
rails-deprecated_sanitizer (1.0.3)
activesupport (>= 4.2.0.alpha)
@@ -604,9 +605,9 @@ GEM
rails-deprecated_sanitizer (>= 1.0.1)
rails-html-sanitizer (1.0.3)
loofah (~> 2.0)
- railties (4.2.5.1)
- actionpack (= 4.2.5.1)
- activesupport (= 4.2.5.1)
+ railties (4.2.5.2)
+ actionpack (= 4.2.5.2)
+ activesupport (= 4.2.5.2)
rake (>= 0.8.7)
thor (>= 0.18.1, < 2.0)
rainbow (2.0.0)
@@ -700,7 +701,7 @@ GEM
rubyntlm (0.5.2)
rubypants (0.2.0)
rufus-scheduler (3.1.10)
- rugged (0.23.3)
+ rugged (0.24.0b13)
safe_yaml (1.0.4)
sanitize (2.1.0)
nokogiri (>= 1.4.4)
@@ -722,7 +723,7 @@ GEM
activesupport (>= 3.1, < 4.3)
select2-rails (3.5.9.3)
thor (~> 0.14)
- sentry-raven (0.15.4)
+ sentry-raven (0.15.6)
faraday (>= 0.7.6)
settingslogic (2.0.9)
sexp_processor (4.6.0)
@@ -763,18 +764,15 @@ GEM
capybara (>= 2.0.0)
railties (>= 3)
spinach (>= 0.4)
- spring (1.3.6)
+ spring (1.6.4)
spring-commands-rspec (1.0.4)
spring (>= 0.9.1)
spring-commands-spinach (1.0.0)
spring (>= 0.9.1)
spring-commands-teaspoon (0.0.2)
spring (>= 0.9.1)
- sprockets (2.12.4)
- hike (~> 1.2)
- multi_json (~> 1.0)
- rack (~> 1.0)
- tilt (~> 1.1, != 1.3.0)
+ sprockets (3.3.5)
+ rack (> 1, < 3)
sprockets-rails (2.3.3)
actionpack (>= 3.0)
activesupport (>= 3.0)
@@ -806,7 +804,7 @@ GEM
rack (~> 1.0)
thor (0.19.1)
thread_safe (0.3.5)
- tilt (1.4.1)
+ tilt (2.0.2)
timfel-krb5-auth (0.8.3)
tinder (1.10.1)
eventmachine (~> 1.0)
@@ -833,7 +831,7 @@ GEM
unf (0.1.4)
unf_ext
unf_ext (0.0.7.1)
- unicorn (4.8.3)
+ unicorn (4.9.0)
kgio (~> 2.6)
rack
raindrops (~> 0.7)
@@ -901,7 +899,7 @@ DEPENDENCIES
cal-heatmap-rails (~> 3.5.0)
capybara (~> 2.4.0)
capybara-screenshot (~> 1.0.0)
- carrierwave (~> 0.9.0)
+ carrierwave (~> 0.10.0)
charlock_holmes (~> 0.7.3)
coffee-rails (~> 4.1.0)
colorize (~> 0.7.0)
@@ -919,7 +917,7 @@ DEPENDENCIES
dropzonejs-rails (~> 0.7.1)
email_reply_parser (~> 0.5.8)
email_spec (~> 1.6.0)
- factory_girl_rails (~> 4.3.0)
+ factory_girl_rails (~> 4.6.0)
ffaker (~> 2.0.0)
flay
flog
@@ -931,8 +929,8 @@ DEPENDENCIES
github-linguist (~> 4.7.0)
github-markup (~> 1.3.1)
gitlab-flowdock-git-hook (~> 1.0.1)
- gitlab_emoji (~> 0.2.0)
- gitlab_git (~> 8.1)
+ gitlab_emoji (~> 0.3.0)
+ gitlab_git (~> 9.0)
gitlab_meta (= 7.0)
gitlab_omniauth-ldap (~> 1.2.1)
gollum-lib (~> 4.1.0)
@@ -954,13 +952,14 @@ DEPENDENCIES
loofah (~> 2.0.3)
mail_room (~> 0.6.1)
method_source (~> 0.8)
+ mini_magick (~> 4.4.0)
minitest (~> 5.7.0)
mousetrap-rails (~> 1.4.6)
mysql2 (~> 0.3.16)
nested_form (~> 0.3.2)
net-ssh (~> 3.0.1)
+ newrelic_rpm (~> 3.14)
nokogiri (~> 1.6.7, >= 1.6.7.2)
- nprogress-rails (~> 0.1.6.7)
oauth2 (~> 1.0.0)
octokit (~> 3.8.0)
omniauth (~> 1.3.1)
@@ -985,7 +984,7 @@ DEPENDENCIES
rack-attack (~> 4.3.1)
rack-cors (~> 0.4.0)
rack-oauth2 (~> 1.2.1)
- rails (= 4.2.5.1)
+ rails (= 4.2.5.2)
rails-deprecated_sanitizer (~> 1.0.3)
raphael-rails (~> 2.1.2)
rblineprof
@@ -1007,7 +1006,7 @@ DEPENDENCIES
sdoc (~> 0.3.20)
seed-fu (~> 2.3.5)
select2-rails (~> 3.5.9)
- sentry-raven
+ sentry-raven (~> 0.15)
settingslogic (~> 2.0.9)
sham_rack
shoulda-matchers (~> 2.8.0)
@@ -1018,11 +1017,11 @@ DEPENDENCIES
six (~> 0.2.0)
slack-notifier (~> 1.2.0)
spinach-rails (~> 0.2.1)
- spring (~> 1.3.6)
+ spring (~> 1.6.4)
spring-commands-rspec (~> 1.0.4)
spring-commands-spinach (~> 1.0.0)
spring-commands-teaspoon (~> 0.0.2)
- sprockets (~> 2.12.3)
+ sprockets (~> 3.3.5)
state_machines-activerecord (~> 0.3.0)
task_list (~> 1.0.2)
teaspoon (~> 1.0.0)
@@ -1034,7 +1033,7 @@ DEPENDENCIES
uglifier (~> 2.7.2)
underscore-rails (~> 1.8.0)
unf (~> 0.1.4)
- unicorn (~> 4.8.2)
+ unicorn (~> 4.9.0)
unicorn-worker-killer (~> 0.4.2)
version_sorter (~> 2.0.0)
virtus (~> 1.0.1)
diff --git a/PROCESS.md b/PROCESS.md
index 5f4d67bc10e..cad45d23df9 100644
--- a/PROCESS.md
+++ b/PROCESS.md
@@ -2,23 +2,39 @@
## Purpose of describing the contributing process
-Below we describe the contributing process to GitLab for two reasons. So that contributors know what to expect from maintainers (possible responses, friendly treatment, etc.). And so that maintainers know what to expect from contributors (use the latest version, ensure that the issue is addressed, friendly treatment, etc.).
+Below we describe the contributing process to GitLab for two reasons. So that
+contributors know what to expect from maintainers (possible responses, friendly
+treatment, etc.). And so that maintainers know what to expect from contributors
+(use the latest version, ensure that the issue is addressed, friendly treatment,
+etc.).
## Common actions
### Issue team
-- Looks for issues without [workflow labels](#how-we-handle-issues) and triages issue
-- Closes invalid issues with a comment (duplicates, [fixed in newer version](#issue-fixed-in-newer-version), [issue report for old version](#issue-report-for-old-version), not a problem in GitLab, etc.)
-- Asks for feedback from issue reporter ([invalid issue reports](#improperly-formatted-issue), [format code](#code-format), etc.)
-- Monitors all issues for feedback (but especially ones commented on since automatically watching them)
+
+- Looks for issues without [workflow labels](#how-we-handle-issues) and triages
+ issue
+- Closes invalid issues with a comment (duplicates,
+ [fixed in newer version](#issue-fixed-in-newer-version),
+ [issue report for old version](#issue-report-for-old-version), not a problem
+ in GitLab, etc.)
+- Asks for feedback from issue reporter
+ ([invalid issue reports](#improperly-formatted-issue),
+ [format code](#code-format), etc.)
+- Monitors all issues for feedback (but especially ones commented on since
+ automatically watching them)
- Closes issues with no feedback from the reporter for two weeks
-### Merge marshal
+### Merge marshall & merge request coach
-- Responds to merge requests the issue team mentions them in and monitors for new merge requests
-- Provides feedback to the merge request submitter to improve the merge request (style, tests, etc.)
-- Mark merge requests 'ready-for-merge' when they meet the contribution guidelines
-- Mention developer(s) based on the [list of members and their specialities](https://about.gitlab.com/core-team/)
+- Responds to merge requests the issue team mentions them in and monitors for
+ new merge requests
+- Provides feedback to the merge request submitter to improve the merge request
+ (style, tests, etc.)
+- Mark merge requests `Ready for Merge` when they meet the
+ [contribution acceptance criteria]
+- Mention developer(s) based on the
+ [list of members and their specialities][team]
- Closes merge requests with no feedback from the reporter for two weeks
## Priorities of the issue team
@@ -30,29 +46,40 @@ Below we describe the contributing process to GitLab for two reasons. So that co
## Mentioning people
-The most important thing is making sure valid issues receive feedback from the development team. Therefore the priority is mentioning developers that can help on those issue. Please select someone with relevant experience from [GitLab core team](https://about.gitlab.com/core-team/). If there is nobody mentioned with that expertise look in the commit history for the affected files to find someone. Avoid mentioning the lead developer, this is the person that is least likely to give a timely response. If the involvement of the lead developer is needed the other core team members will mention this person.
+The most important thing is making sure valid issues receive feedback from the
+development team. Therefore the priority is mentioning developers that can help
+on those issue. Please select someone with relevant experience from
+[GitLab core team][core-team]. If there is nobody mentioned with that expertise
+look in the commit history for the affected files to find someone. Avoid
+mentioning the lead developer, this is the person that is least likely to give a
+timely response. If the involvement of the lead developer is needed the other
+core team members will mention this person.
## Workflow labels
-Workflow labels are purposely not very detailed since that would be hard to keep updated as you would need to re-evaluate them after every comment. We optionally use functional labels on demand when want to group related issues to get an overview (for example all issues related to RVM, to tackle them in one go) and to add details to the issue.
-
-- *Awaiting feedback*: Feedback pending from the reporter
-- *Awaiting confirmation of fix*: The issue should already be solved in **master** (generally you can avoid this workflow item and just close the issue right away)
-- *Attached MR*: There is a MR attached and the discussion should happen there
- - We need to let issues stay in sync with the MR's. We can do this with a "Closing #XXXX" or "Fixes #XXXX" comment in the MR. We can't close the issue when there is a merge request because sometimes a MR is not good and we just close the MR, then the issue must stay.
-- *Developer*: needs help from a developer
-- *UX* needs needs help from a UX designer
-- *Frontend* needs help from a Front-end engineer
-- *Graphics* needs help from a Graphics designer
-- *up-for-grabs* is an issue suitable for first-time contributors, of reasonable difficulty and size. Not exclusive with other labels.
-- *feature proposal* is a proposal for a new feature for GitLab. People are encouraged to vote
+Workflow labels are purposely not very detailed since that would be hard to keep
+updated as you would need to re-evaluate them after every comment. We optionally
+use functional labels on demand when want to group related issues to get an
+overview (for example all issues related to RVM, to tackle them in one go) and
+to add details to the issue.
+
+- ~"Awaiting Feedback" Feedback pending from the reporter
+- ~UX needs help from a UX designer
+- ~Frontend needs help from a Front-end engineer. Please follow the
+ ["Implement design & UI elements" guidelines].
+- ~up-for-grabs is an issue suitable for first-time contributors, of reasonable difficulty and size. Not exclusive with other labels.
+- ~"feature proposal" is a proposal for a new feature for GitLab. People are encouraged to vote
in support or comment for further detail. Do not use `feature request`.
-
+- ~bug is an issue reporting undesirable or incorrect behavior.
+- ~customer is an issue reported by enterprise subscribers. This label should
+be accompanied by *bug* or *feature proposal* labels.
Example workflow: when a UX designer provided a design but it needs frontend work they remove the UX label and add the frontend label.
## Functional labels
-These labels describe what development specialities are involved such as: PostgreSQL, UX, LDAP.
+These labels describe what development specialities are involved such as: `CI`,
+`Core`, `Documentation`, `Frontend`, `Issues`, `Merge Requests`, `Omnibus`,
+`Release`, `Repository`, `UX`.
## Assigning issues
@@ -60,21 +87,29 @@ If an issue is complex and needs the attention of a specific person, assignment
## Label colors
-- Light orange `#fef2c0`: workflow labels for issue team members (awaiting feedback, awaiting confirmation of fix)
-- Bright orange `#eb6420`: workflow labels for core team members (attached MR, awaiting developer action/feedback)
-- Light blue `#82C5FF`: functional labels
-- Green labels `#009800`: issues that can generally be ignored. For example, issues given the following labels normally can be closed immediately:
- - Support (see copy & paste response: [Support requests and configuration questions](#support-requests-and-configuration-questions)
+- Light orange `#fef2c0`: workflow labels for issue team members (awaiting
+ feedback, awaiting confirmation of fix)
+- Bright orange `#eb6420`: workflow labels for core team members (attached MR,
+ awaiting developer action/feedback)
+- Light blue `#82C5FF`: functional labels
+- Green labels `#009800`: issues that can generally be ignored. For example,
+ issues given the following labels normally can be closed immediately:
+ - Support (see copy & paste response:
+ [Support requests and configuration questions](#support-requests-and-configuration-questions)
## Be kind
-Be kind to people trying to contribute. Be aware that people may be a non-native English speaker, they might not understand things or they might be very sensitive as to how you word things. Use Emoji to express your feelings (heart, star, smile, etc.). Some good tips about giving feedback to merge requests is in the [Thoughtbot code review guide](https://github.com/thoughtbot/guides/tree/master/code-review).
+Be kind to people trying to contribute. Be aware that people may be a non-native
+English speaker, they might not understand things or they might be very
+sensitive as to how you word things. Use Emoji to express your feelings (heart,
+star, smile, etc.). Some good tips about giving feedback to merge requests is in
+the [Thoughtbot code review guide].
## Copy & paste responses
### Improperly formatted issue
-Thanks for the issue report. Please reformat your issue to conform to the issue tracker guidelines found in our \[contributing guidelines\]\(https://gitlab.com/gitlab-org/gitlab-ce/blob/master/CONTRIBUTING.md#issue-tracker-guidelines).
+Thanks for the issue report. Please reformat your issue to conform to the \[contributing guidelines\]\(https://gitlab.com/gitlab-org/gitlab-ce/blob/master/CONTRIBUTING.md#issue-tracker-guidelines).
### Issue report for old version
@@ -110,11 +145,11 @@ This merge request has been closed because a request for more information has no
### Accepting merge requests
-Is there an issue on the [issue tracker](https://gitlab.com/gitlab-org/gitlab-ce/issues)
-that is similar to this?
-Could you please link it here?
+Is there an issue on the
+\[issue tracker\]\(https://gitlab.com/gitlab-org/gitlab-ce/issues) that is
+similar to this? Could you please link it here?
Please be aware that new functionality that is not marked
-[accepting merge requests](https://gitlab.com/gitlab-org/gitlab-ce/issues?milestone_id=&scope=all&sort=created_desc&state=opened&utf8=%E2%9C%93&assignee_id=&author_id=&milestone_title=&label_name=Accepting+Merge+Requests)
+\[accepting merge requests\]\(https://gitlab.com/gitlab-org/gitlab-ce/issues?milestone_id=&scope=all&sort=created_desc&state=opened&utf8=%E2%9C%93&assignee_id=&author_id=&milestone_title=&label_name=Accepting+Merge+Requests)
might not make it into GitLab.
### Only accepting merge requests with green tests
@@ -129,4 +164,10 @@ rebase with master to see if that solves the issue.
We are currently in the process of closing down the issue tracker on GitHub, to
prevent duplication with the GitLab.com issue tracker.
Since this is an older issue I'll be closing this for now. If you think this is
-still an issue I encourage you to open it on the \[GitLab.com issue tracker\](https://gitlab.com/gitlab-org/gitlab-ce/issues).
+still an issue I encourage you to open it on the \[GitLab.com issue tracker\]\(https://gitlab.com/gitlab-org/gitlab-ce/issues).
+
+[core-team]: https://about.gitlab.com/core-team/
+[team]: https://about.gitlab.com/team/
+[contribution acceptance criteria]: https://gitlab.com/gitlab-org/gitlab-ce/blob/master/CONTRIBUTING.md#contribution-acceptance-criteria
+["Implement design & UI elements" guidelines]: https://gitlab.com/gitlab-org/gitlab-ce/blob/master/CONTRIBUTING.md#implement-design-ui-elements
+[Thoughtbot code review guide]: https://github.com/thoughtbot/guides/tree/master/code-review
diff --git a/README.md b/README.md
index 22dbf841bdc..3ec1d4a776c 100644
--- a/README.md
+++ b/README.md
@@ -67,7 +67,7 @@ Instructions on how to start GitLab and how to run the tests can be found in the
GitLab is a Ruby on Rails application that runs on the following software:
- Ubuntu/Debian/CentOS/RHEL
-- Ruby (MRI) 2.1 or 2.2
+- Ruby (MRI) 2.1
- Git 1.7.10+
- Redis 2.8+
- MySQL or PostgreSQL
diff --git a/Rakefile b/Rakefile
index 35b2f05cbb4..5dd389d5678 100755
--- a/Rakefile
+++ b/Rakefile
@@ -4,4 +4,7 @@
require File.expand_path('../config/application', __FILE__)
+relative_url_conf = File.expand_path('../config/initializers/relative_url', __FILE__)
+require relative_url_conf if File.exist?("#{relative_url_conf}.rb")
+
Gitlab::Application.load_tasks
diff --git a/VERSION b/VERSION
index ffec98087cb..cac7d91adda 100644
--- a/VERSION
+++ b/VERSION
@@ -1 +1 @@
-8.5.0-pre
+8.6.0-pre
diff --git a/app/assets/images/emoji.png b/app/assets/images/emoji.png
index a8ad7b6eab6..1e7cf79ea45 100644
--- a/app/assets/images/emoji.png
+++ b/app/assets/images/emoji.png
Binary files differ
diff --git a/app/assets/images/emoji@2x.png b/app/assets/images/emoji@2x.png
new file mode 100644
index 00000000000..74d67f7520d
--- /dev/null
+++ b/app/assets/images/emoji@2x.png
Binary files differ
diff --git a/app/assets/javascripts/activities.js.coffee b/app/assets/javascripts/activities.js.coffee
index 3b6b453ac51..5092e824e65 100644
--- a/app/assets/javascripts/activities.js.coffee
+++ b/app/assets/javascripts/activities.js.coffee
@@ -1,7 +1,7 @@
class @Activities
constructor: ->
Pager.init 20, true
- $(".event-filter a").bind "click", (event) =>
+ $(".event-filter-link").on "click", (event) =>
event.preventDefault()
@toggleFilter($(event.currentTarget))
@reloadActivities()
@@ -12,18 +12,10 @@ class @Activities
toggleFilter: (sender) ->
- sender.closest('li').toggleClass "active"
+ $('.event-filter .active').removeClass "active"
event_filters = $.cookie("event_filter")
filter = sender.attr("id").split("_")[0]
- if event_filters
- event_filters = event_filters.split(",")
- else
- event_filters = new Array()
+ $.cookie "event_filter", (if event_filters isnt filter then filter else ""), { path: '/' }
- index = event_filters.indexOf(filter)
- if index is -1
- event_filters.push filter
- else
- event_filters.splice index, 1
-
- $.cookie "event_filter", event_filters.join(","), { path: '/' }
+ if event_filters isnt filter
+ sender.closest('li').toggleClass "active"
diff --git a/app/assets/javascripts/application.js.coffee b/app/assets/javascripts/application.js.coffee
index 367bd098bfd..321da10a009 100644
--- a/app/assets/javascripts/application.js.coffee
+++ b/app/assets/javascripts/application.js.coffee
@@ -31,8 +31,6 @@
#= require ace/ace
#= require ace/ext-searchbox
#= require underscore
-#= require nprogress
-#= require nprogress-turbolinks
#= require dropzone
#= require mousetrap
#= require mousetrap/pause
@@ -44,6 +42,7 @@
#= require jquery.nicescroll
#= require_tree .
#= require fuzzaldrin-plus
+#= require cropper.js
window.slugify = (text) ->
text.replace(/[^-a-zA-Z0-9]+/g, '_').toLowerCase()
@@ -210,7 +209,7 @@ $ ->
$this = $(this)
$this.attr 'value', $this.val()
return
-
+
$(document)
.off 'keyup', 'input[type="search"]'
.on 'keyup', 'input[type="search"]' , (e) ->
@@ -221,41 +220,41 @@ $ ->
.off 'breakpoint:change'
.on 'breakpoint:change', (e, breakpoint) ->
if breakpoint is 'sm' or breakpoint is 'xs'
- $gutterIcon = $('.gutter-toggle').find('i')
+ $gutterIcon = $('aside .gutter-toggle').find('i')
if $gutterIcon.hasClass('fa-angle-double-right')
$gutterIcon.closest('a').trigger('click')
$(document)
.off 'click', 'aside .gutter-toggle'
- .on 'click', 'aside .gutter-toggle', (e) ->
+ .on 'click', 'aside .gutter-toggle', (e, triggered) ->
e.preventDefault()
$this = $(this)
$thisIcon = $this.find 'i'
+ $allGutterToggleIcons = $('.gutter-toggle i')
if $thisIcon.hasClass('fa-angle-double-right')
- $thisIcon
+ $allGutterToggleIcons
.removeClass('fa-angle-double-right')
.addClass('fa-angle-double-left')
- $this
- .closest('aside')
+ $('aside.right-sidebar')
.removeClass('right-sidebar-expanded')
.addClass('right-sidebar-collapsed')
$('.page-with-sidebar')
.removeClass('right-sidebar-expanded')
.addClass('right-sidebar-collapsed')
else
- $thisIcon
+ $allGutterToggleIcons
.removeClass('fa-angle-double-left')
.addClass('fa-angle-double-right')
- $this
- .closest('aside')
+ $('aside.right-sidebar')
.removeClass('right-sidebar-collapsed')
.addClass('right-sidebar-expanded')
$('.page-with-sidebar')
.removeClass('right-sidebar-collapsed')
.addClass('right-sidebar-expanded')
- $.cookie("collapsed_gutter",
- $('.right-sidebar')
- .hasClass('right-sidebar-collapsed'), { path: '/' })
+ if not triggered
+ $.cookie("collapsed_gutter",
+ $('.right-sidebar')
+ .hasClass('right-sidebar-collapsed'), { path: '/' })
bootstrapBreakpoint = undefined;
checkBootstrapBreakpoints = ->
diff --git a/app/assets/javascripts/autosave.js.coffee b/app/assets/javascripts/autosave.js.coffee
index 5d3fe81da74..28f8e103664 100644
--- a/app/assets/javascripts/autosave.js.coffee
+++ b/app/assets/javascripts/autosave.js.coffee
@@ -16,11 +16,11 @@ class @Autosave
try
text = window.localStorage.getItem @key
- catch
+ catch e
return
@field.val text if text?.length > 0
- @field.trigger "input"
+ @field.trigger "input"
save: ->
return unless window.localStorage?
@@ -35,5 +35,5 @@ class @Autosave
reset: ->
return unless window.localStorage?
- try
+ try
window.localStorage.removeItem @key
diff --git a/app/assets/javascripts/awards_handler.coffee b/app/assets/javascripts/awards_handler.coffee
index 047df4786a9..8f89d3e61a2 100644
--- a/app/assets/javascripts/awards_handler.coffee
+++ b/app/assets/javascripts/awards_handler.coffee
@@ -1,10 +1,10 @@
class @AwardsHandler
constructor: (@post_emoji_url, @noteable_type, @noteable_id, @aliases) ->
- $(".add-award").click (event)->
+ $(".add-award").click (event) =>
event.stopPropagation()
event.preventDefault()
- $(".emoji-menu").show()
- $("#emoji_search").focus()
+
+ @showEmojiMenu()
$("html").on 'click', (event) ->
if !$(event.target).closest(".emoji-menu").length
@@ -14,6 +14,16 @@ class @AwardsHandler
@renderFrequentlyUsedBlock()
@setupSearch()
+ showEmojiMenu: ->
+ if $(".emoji-menu").length
+ $(".emoji-menu").show()
+ $("#emoji_search").focus()
+ else
+ $.get "/emojis", (response) ->
+ $(".add-award").after response
+ $(".emoji-menu").show()
+ $("#emoji_search").focus()
+
addAward: (emoji) ->
emoji = @normilizeEmojiName(emoji)
@postEmoji emoji, =>
@@ -49,10 +59,11 @@ class @AwardsHandler
counter.text(parseInt(counter.text()) - 1)
emojiIcon.removeClass("active")
@removeMeFromAuthorList(emoji)
- else if emoji =="thumbsup" || emoji == "thumbsdown"
+ else if emoji == "thumbsup" || emoji == "thumbsdown"
emojiIcon.tooltip("destroy")
counter.text(0)
emojiIcon.removeClass("active")
+ @removeMeFromAuthorList(emoji)
else
emojiIcon.tooltip("destroy")
emojiIcon.remove()
diff --git a/app/assets/javascripts/behaviors/quick_submit.js.coffee b/app/assets/javascripts/behaviors/quick_submit.js.coffee
index 4ec8531d580..6e29d374267 100644
--- a/app/assets/javascripts/behaviors/quick_submit.js.coffee
+++ b/app/assets/javascripts/behaviors/quick_submit.js.coffee
@@ -1,29 +1,52 @@
# Quick Submit behavior
#
-# When an input field with the `js-quick-submit` class receives a "Meta+Enter"
-# (Mac) or "Ctrl+Enter" (Linux/Windows) key combination, its parent form is
-# submitted.
+# When a child field of a form with a `js-quick-submit` class receives a
+# "Meta+Enter" (Mac) or "Ctrl+Enter" (Linux/Windows) key combination, the form
+# is submitted.
#
#= require extensions/jquery
#
# ### Example Markup
#
-# <form action="/foo">
-# <input type="text" class="js-quick-submit" />
-# <textarea class="js-quick-submit"></textarea>
+# <form action="/foo" class="js-quick-submit">
+# <input type="text" />
+# <textarea></textarea>
+# <input type="submit" value="Submit" />
# </form>
#
+isMac = ->
+ navigator.userAgent.match(/Macintosh/)
+
+keyCodeIs = (e, keyCode) ->
+ return false if (e.originalEvent && e.originalEvent.repeat) || e.repeat
+ return e.keyCode == keyCode
+
$(document).on 'keydown.quick_submit', '.js-quick-submit', (e) ->
- return if (e.originalEvent && e.originalEvent.repeat) || e.repeat
- return unless e.keyCode == 13 # Enter
+ return unless keyCodeIs(e, 13) # Enter
- if navigator.userAgent.match(/Macintosh/)
- return unless (e.metaKey && !e.altKey && !e.ctrlKey && !e.shiftKey)
- else
- return unless (e.ctrlKey && !e.altKey && !e.metaKey && !e.shiftKey)
+ return unless (e.metaKey && !e.altKey && !e.ctrlKey && !e.shiftKey) || (e.ctrlKey && !e.altKey && !e.metaKey && !e.shiftKey)
e.preventDefault()
$form = $(e.target).closest('form')
$form.find('input[type=submit], button[type=submit]').disable()
$form.submit()
+
+# If the user tabs to a submit button on a `js-quick-submit` form, display a
+# tooltip to let them know they could've used the hotkey
+$(document).on 'keyup.quick_submit', '.js-quick-submit input[type=submit], .js-quick-submit button[type=submit]', (e) ->
+ return unless keyCodeIs(e, 9) # Tab
+
+ if isMac()
+ title = "You can also press &#8984;-Enter"
+ else
+ title = "You can also press Ctrl-Enter"
+
+ $this = $(@)
+ $this.tooltip(
+ container: 'body'
+ html: 'true'
+ placement: 'auto top'
+ title: title
+ trigger: 'manual'
+ ).tooltip('show').one('blur', -> $this.tooltip('hide'))
diff --git a/app/assets/javascripts/dashboard.js.coffee b/app/assets/javascripts/dashboard.js.coffee
deleted file mode 100644
index 62143e66cfe..00000000000
--- a/app/assets/javascripts/dashboard.js.coffee
+++ /dev/null
@@ -1,31 +0,0 @@
-@Dashboard =
- init: ->
- $(".projects-list-filter").off('keyup')
- this.initSearch()
-
- initSearch: ->
- @timer = null
- $(".projects-list-filter").on('keyup', ->
- clearTimeout(@timer)
- @timer = setTimeout(Dashboard.filterResults, 500)
- )
-
- filterResults: =>
- $('.projects-list-holder').fadeTo(250, 0.5)
-
- form = null
- form = $("form#project-filter-form")
- search = $(".projects-list-filter").val()
- project_filter_url = form.attr('action') + '?' + form.serialize()
-
- $.ajax
- type: "GET"
- url: form.attr('action')
- data: form.serialize()
- complete: ->
- $('.projects-list-holder').fadeTo(250, 1)
- success: (data) ->
- $('.projects-list-holder').replaceWith(data.html)
- # Change url so if user reload a page - search results are saved
- history.replaceState {page: project_filter_url}, document.title, project_filter_url
- dataType: "json"
diff --git a/app/assets/javascripts/dispatcher.js.coffee b/app/assets/javascripts/dispatcher.js.coffee
index b17f8e51470..54b28f2dd8d 100644
--- a/app/assets/javascripts/dispatcher.js.coffee
+++ b/app/assets/javascripts/dispatcher.js.coffee
@@ -16,8 +16,6 @@ class Dispatcher
shortcut_handler = null
switch page
- when 'explore:projects:index', 'explore:projects:starred', 'explore:projects:trending'
- Dashboard.init()
when 'projects:issues:index'
Issues.init()
shortcut_handler = new ShortcutsNavigation()
@@ -25,7 +23,7 @@ class Dispatcher
new Issue()
shortcut_handler = new ShortcutsIssuable()
new ZenMode()
- when 'projects:milestones:show'
+ when 'projects:milestones:show', 'groups:milestones:show', 'dashboard:milestones:show'
new Milestone()
when 'projects:milestones:new', 'projects:milestones:edit'
new ZenMode()
@@ -59,8 +57,6 @@ class Dispatcher
when 'projects:merge_requests:index'
shortcut_handler = new ShortcutsNavigation()
MergeRequests.init()
- when 'dashboard:show', 'root:show'
- Dashboard.init()
when 'dashboard:activity'
new Activities()
when 'dashboard:projects:starred'
@@ -76,6 +72,8 @@ class Dispatcher
shortcut_handler = new ShortcutsNavigation()
when 'projects:show'
shortcut_handler = new ShortcutsNavigation()
+
+ new TreeView() if $('#tree-slider').length
when 'groups:show'
new Activities()
shortcut_handler = new ShortcutsNavigation()
@@ -88,10 +86,11 @@ class Dispatcher
when 'groups:new', 'groups:edit', 'admin:groups:edit', 'admin:groups:new'
new GroupAvatar()
when 'projects:tree:show'
+ shortcut_handler = new ShortcutsNavigation()
new TreeView()
when 'projects:find_file:show'
shortcut_handler = true
- when 'projects:blob:show'
+ when 'projects:blob:show', 'projects:blame:show'
new LineHighlighter()
shortcut_handler = new ShortcutsNavigation()
when 'projects:labels:new', 'projects:labels:edit'
@@ -104,9 +103,6 @@ class Dispatcher
new ProjectFork()
when 'projects:artifacts:browse'
new BuildArtifacts()
- when 'users:show'
- new User()
- new Activities()
switch path.first()
when 'admin'
diff --git a/app/assets/javascripts/issuable_context.js.coffee b/app/assets/javascripts/issuable_context.js.coffee
index d17b1123418..e52b73f94f6 100644
--- a/app/assets/javascripts/issuable_context.js.coffee
+++ b/app/assets/javascripts/issuable_context.js.coffee
@@ -15,3 +15,5 @@ class @IssuableContext
block.find('.selectbox').show()
block.find('.value').hide()
block.find('.js-select2').select2("open")
+
+ $(".right-sidebar").niceScroll()
diff --git a/app/assets/javascripts/logo.js.coffee b/app/assets/javascripts/logo.js.coffee
index 35b2fbbba07..d14b7139237 100644
--- a/app/assets/javascripts/logo.js.coffee
+++ b/app/assets/javascripts/logo.js.coffee
@@ -1,4 +1,4 @@
-NProgress.configure(showSpinner: false)
+Turbolinks.enableProgressBar();
defaultClass = 'tanuki-shape'
pieces = [
diff --git a/app/assets/javascripts/merge_request_tabs.js.coffee b/app/assets/javascripts/merge_request_tabs.js.coffee
index b10e1db7f3f..58373ba87a5 100644
--- a/app/assets/javascripts/merge_request_tabs.js.coffee
+++ b/app/assets/javascripts/merge_request_tabs.js.coffee
@@ -70,6 +70,7 @@ class @MergeRequestTabs
@loadCommits($target.attr('href'))
else if action == 'diffs'
@loadDiff($target.attr('href'))
+ @shrinkView()
else if action == 'builds'
@loadBuilds($target.attr('href'))
@@ -145,7 +146,9 @@ class @MergeRequestTabs
url: "#{source}.json" + @_location.search
success: (data) =>
document.querySelector("div#diffs").innerHTML = data.html
+ $('.js-timeago').timeago()
$('div#diffs .js-syntax-highlight').syntaxHighlight()
+ @expandViewContainer() if @diffViewType() is 'parallel'
@diffsLoaded = true
@scrollToElement("#diffs")
@@ -177,3 +180,21 @@ class @MergeRequestTabs
options = $.extend({}, defaults, options)
$.ajax(options)
+
+ # Returns diff view type
+ diffViewType: ->
+ $('.inline-parallel-buttons a.active').data('view-type')
+
+ expandViewContainer: ->
+ $('.container-fluid').removeClass('container-limited')
+
+ shrinkView: ->
+ $gutterIcon = $('.gutter-toggle i')
+
+ # Wait until listeners are set
+ setTimeout( ->
+ # Only when sidebar is collapsed
+ if $gutterIcon.is('.fa-angle-double-right')
+ $gutterIcon.closest('a').trigger('click',[true])
+ , 0)
+
diff --git a/app/assets/javascripts/milestone.js.coffee b/app/assets/javascripts/milestone.js.coffee
index d644d50b669..0037a3a21c2 100644
--- a/app/assets/javascripts/milestone.js.coffee
+++ b/app/assets/javascripts/milestone.js.coffee
@@ -62,14 +62,24 @@ class @Milestone
dataType: "json"
constructor: ->
+ oldMouseStart = $.ui.sortable.prototype._mouseStart
+ $.ui.sortable.prototype._mouseStart = (event, overrideHandle, noActivation) ->
+ this._trigger "beforeStart", event, this._uiHash()
+ oldMouseStart.apply this, [event, overrideHandle, noActivation]
+
@bindIssuesSorting()
@bindMergeRequestSorting()
+ @bindTabsSwitching()
bindIssuesSorting: ->
$("#issues-list-unassigned, #issues-list-ongoing, #issues-list-closed").sortable(
connectWith: ".issues-sortable-list",
dropOnEmpty: true,
items: "li:not(.ui-sort-disabled)",
+ beforeStart: (event, ui) ->
+ $(".issues-sortable-list").css "min-height", ui.item.outerHeight()
+ stop: (event, ui) ->
+ $(".issues-sortable-list").css "min-height", "0px"
update: (event, ui) ->
data = $(this).sortable("serialize")
Milestone.sortIssues(data)
@@ -94,11 +104,24 @@ class @Milestone
).disableSelection()
+ bindTabsSwitching: ->
+ $('a[data-toggle="tab"]').on 'show.bs.tab', (e) ->
+ currentTabClass = $(e.target).data('show')
+ previousTabClass = $(e.relatedTarget).data('show')
+
+ $(previousTabClass).hide()
+ $(currentTabClass).removeClass('hidden')
+ $(currentTabClass).show()
+
bindMergeRequestSorting: ->
$("#merge_requests-list-unassigned, #merge_requests-list-ongoing, #merge_requests-list-closed").sortable(
connectWith: ".merge_requests-sortable-list",
dropOnEmpty: true,
items: "li:not(.ui-sort-disabled)",
+ beforeStart: (event, ui) ->
+ $(".merge_requests-sortable-list").css "min-height", ui.item.outerHeight()
+ stop: (event, ui) ->
+ $(".merge_requests-sortable-list").css "min-height", "0px"
update: (event, ui) ->
data = $(this).sortable("serialize")
Milestone.sortMergeRequests(data)
diff --git a/app/assets/javascripts/notes.js.coffee b/app/assets/javascripts/notes.js.coffee
index 3347ab65c90..c95ead22e6c 100644
--- a/app/assets/javascripts/notes.js.coffee
+++ b/app/assets/javascripts/notes.js.coffee
@@ -16,11 +16,13 @@ class @Notes
@view = view
@noteable_url = document.URL
@notesCountBadge ||= $(".issuable-details").find(".notes-tab .badge")
+ @basePollingInterval = 15000
+ @maxPollingSteps = 4
- @initRefresh()
- @setupMainTargetNoteForm()
@cleanBinding()
@addBinding()
+ @setPollingInterval()
+ @setupMainTargetNoteForm()
@initTaskList()
addBinding: ->
@@ -37,7 +39,7 @@ class @Notes
# Reopen and close actions for Issue/MR combined with note form submit
$(document).on "click", ".js-comment-button", @updateCloseButton
- $(document).on "keyup", ".js-note-text", @updateTargetButtons
+ $(document).on "keyup input", ".js-note-text", @updateTargetButtons
# remove a note (in general)
$(document).on "click", ".js-note-delete", @removeNote
@@ -91,9 +93,11 @@ class @Notes
clearInterval(Notes.interval)
Notes.interval = setInterval =>
@refresh()
- , 15000
+ , @pollingInterval
refresh: ->
+ return if @refreshing is true
+ refreshing = true
if not document.hidden and document.URL.indexOf(@noteable_url) is 0
@getContent()
@@ -105,12 +109,31 @@ class @Notes
success: (data) =>
notes = data.notes
@last_fetched_at = data.last_fetched_at
+ @setPollingInterval(data.notes.length)
$.each notes, (i, note) =>
if note.discussion_with_diff_html?
@renderDiscussionNote(note)
else
@renderNote(note)
+ always: =>
+ @refreshing = false
+
+ ###
+ Increase @pollingInterval up to 120 seconds on every function call,
+ if `shouldReset` has a truthy value, 'null' or 'undefined' the variable
+ will reset to @basePollingInterval.
+
+ Note: this function is used to gradually increase the polling interval
+ if there aren't new notes coming from the server
+ ###
+ setPollingInterval: (shouldReset = true) ->
+ nthInterval = @basePollingInterval * Math.pow(2, @maxPollingSteps - 1)
+ if shouldReset
+ @pollingInterval = @basePollingInterval
+ else if @pollingInterval < nthInterval
+ @pollingInterval *= 2
+ @initRefresh()
###
Render note in main comments area.
diff --git a/app/assets/javascripts/pager.js.coffee b/app/assets/javascripts/pager.js.coffee
index d639303aed3..0ff83b7f0c8 100644
--- a/app/assets/javascripts/pager.js.coffee
+++ b/app/assets/javascripts/pager.js.coffee
@@ -1,6 +1,7 @@
@Pager =
init: (@limit = 0, preload, @disable = false) ->
- @loading = $(".loading")
+ @loading = $('.loading').first()
+
if preload
@offset = 0
@getOld()
diff --git a/app/assets/javascripts/profile.js.coffee b/app/assets/javascripts/profile.js.coffee
index bb0b66b86e1..9110b732adc 100644
--- a/app/assets/javascripts/profile.js.coffee
+++ b/app/assets/javascripts/profile.js.coffee
@@ -16,11 +16,58 @@ class @Profile
$('.update-notifications').on 'ajax:complete', ->
$(this).find('.btn-save').enable()
- $('.js-choose-user-avatar-button').bind "click", ->
- form = $(this).closest("form")
- form.find(".js-user-avatar-input").click()
+ # Avatar management
+
+ $avatarInput = $('.js-user-avatar-input')
+ $filename = $('.js-avatar-filename')
+ $modalCrop = $('.modal-profile-crop')
+ $modalCropImg = $('.modal-profile-crop-image')
+
+ $('.js-choose-user-avatar-button').on "click", ->
+ $form = $(this).closest("form")
+ $form.find(".js-user-avatar-input").click()
+
+ $modalCrop.on 'shown.bs.modal', ->
+ setTimeout ( -> # The cropper must be asynchronously initialized
+ $modalCropImg.cropper
+ aspectRatio: 1
+ modal: false
+ scalable: false
+ rotatable: false
+ zoomable: false
+
+ crop: (event) ->
+ ['x', 'y'].forEach (key) ->
+ $("#user_avatar_crop_#{key}").val(Math.floor(event[key]))
+ $("#user_avatar_crop_size").val(Math.floor(event.width))
+ ), 0
+
+ $modalCrop.on 'hidden.bs.modal', ->
+ $modalCropImg.attr('src', '').cropper('destroy')
+ $avatarInput.val('')
+ $filename.text($filename.data('label'))
- $('.js-user-avatar-input').bind "change", ->
+ $('.js-upload-user-avatar').on 'click', ->
+ $('.edit-user').submit()
+
+ $avatarInput.on "change", ->
form = $(this).closest("form")
filename = $(this).val().replace(/^.*[\\\/]/, '')
- form.find(".js-avatar-filename").text(filename)
+ $filename.data('label', $filename.text()).text(filename)
+
+ reader = new FileReader
+
+ reader.onload = (event) ->
+ $modalCrop.modal('show')
+ $modalCropImg.attr('src', event.target.result)
+
+ fileData = reader.readAsDataURL(this.files[0])
+
+$ ->
+ # Extract the SSH Key title from its comment
+ $(document).on 'focusout.ssh_key', '#key_key', ->
+ $title = $('#key_title')
+ comment = $(@).val().match(/^\S+ \S+ (.+)\n?$/)
+
+ if comment && comment.length > 1 && $title.val() == ''
+ $title.val(comment[1]).change()
diff --git a/app/assets/javascripts/projects_list.js.coffee b/app/assets/javascripts/projects_list.js.coffee
index eab34be652a..ed5206368ce 100644
--- a/app/assets/javascripts/projects_list.js.coffee
+++ b/app/assets/javascripts/projects_list.js.coffee
@@ -1,26 +1,31 @@
-class @ProjectsList
- constructor: ->
- $(".projects-list .js-expand").on 'click', (e) ->
- e.preventDefault()
- list = $(this).closest('.projects-list')
+@ProjectsList =
+ init: ->
+ $(".projects-list-filter").off('keyup')
+ this.initSearch()
- $("#filter_projects").on 'keyup', ->
- ProjectsList.filter_results($("#filter_projects"))
+ initSearch: ->
+ @timer = null
+ $(".projects-list-filter").on('keyup', ->
+ clearTimeout(@timer)
+ @timer = setTimeout(ProjectsList.filterResults, 500)
+ )
- @filter_results: ($element) ->
- terms = $element.val()
- filterSelector = $element.data('filter-selector') || 'span.filter-title'
+ filterResults: =>
+ $('.projects-list-holder').fadeTo(250, 0.5)
- if not terms
- $(".projects-list li").show()
- $('.gl-pagination').show()
- else
- $(".projects-list li").each (index) ->
- $this = $(this)
- name = $this.find(filterSelector).text()
+ form = null
+ form = $("form#project-filter-form")
+ search = $(".projects-list-filter").val()
+ project_filter_url = form.attr('action') + '?' + form.serialize()
- if name.toLowerCase().indexOf(terms.toLowerCase()) == -1
- $this.hide()
- else
- $this.show()
- $('.gl-pagination').hide()
+ $.ajax
+ type: "GET"
+ url: form.attr('action')
+ data: form.serialize()
+ complete: ->
+ $('.projects-list-holder').fadeTo(250, 1)
+ success: (data) ->
+ $('.projects-list-holder').replaceWith(data.html)
+ # Change url so if user reload a page - search results are saved
+ history.replaceState {page: project_filter_url}, document.title, project_filter_url
+ dataType: "json"
diff --git a/app/assets/javascripts/shortcuts.js.coffee b/app/assets/javascripts/shortcuts.js.coffee
index f141fb69c3e..9c7c2474aa4 100644
--- a/app/assets/javascripts/shortcuts.js.coffee
+++ b/app/assets/javascripts/shortcuts.js.coffee
@@ -13,8 +13,10 @@ class @Shortcuts
if $('#modal-shortcuts').length > 0
$('#modal-shortcuts').modal('show')
else
+ url = '/help/shortcuts'
+ url = gon.relative_url_root + url if gon.relative_url_root?
$.ajax(
- url: '/help/shortcuts',
+ url: url,
dataType: 'script',
success: (e) ->
if location and location.length > 0
diff --git a/app/assets/javascripts/shortcuts_issuable.coffee b/app/assets/javascripts/shortcuts_issuable.coffee
index cefa1857d7f..bbf02f1db24 100644
--- a/app/assets/javascripts/shortcuts_issuable.coffee
+++ b/app/assets/javascripts/shortcuts_issuable.coffee
@@ -24,6 +24,10 @@ class @ShortcutsIssuable extends ShortcutsNavigation
@nextIssue()
return false
)
+ Mousetrap.bind('e', =>
+ @editIssue()
+ return false
+ )
if isMergeRequest
@@ -63,3 +67,7 @@ class @ShortcutsIssuable extends ShortcutsNavigation
# Focus the input field
replyField.focus()
+
+ editIssue: ->
+ $editBtn = $('.issuable-edit')
+ Turbolinks.visit($editBtn.attr('href'))
diff --git a/app/assets/javascripts/sidebar.js.coffee b/app/assets/javascripts/sidebar.js.coffee
index ae59480af9e..cff309c5972 100644
--- a/app/assets/javascripts/sidebar.js.coffee
+++ b/app/assets/javascripts/sidebar.js.coffee
@@ -8,4 +8,10 @@ $(document).on("click", '.toggle-nav-collapse', (e) ->
$('.sidebar-wrapper').toggleClass("sidebar-collapsed sidebar-expanded")
$('.toggle-nav-collapse i').toggleClass("fa-angle-right fa-angle-left")
$.cookie("collapsed_nav", $('.page-with-sidebar').hasClass(collapsed), { path: '/' })
+
+ setTimeout ( ->
+ niceScrollBars = $('.nicescroll').niceScroll();
+ niceScrollBars.updateScrollBar();
+ ), 300
+
)
diff --git a/app/assets/javascripts/user.js.coffee b/app/assets/javascripts/user.js.coffee
index ec4271b092c..2882a90d118 100644
--- a/app/assets/javascripts/user.js.coffee
+++ b/app/assets/javascripts/user.js.coffee
@@ -1,10 +1,17 @@
class @User
- constructor: ->
+ constructor: (@opts) ->
$('.profile-groups-avatars').tooltip("placement": "top")
- new ProjectsList()
+
+ @initTabs()
$('.hide-project-limit-message').on 'click', (e) ->
path = '/'
$.cookie('hide_project_limit_message', 'false', { path: path })
$(@).parents('.project-limit-message').remove()
e.preventDefault()
+
+ initTabs: ->
+ new UserTabs(
+ parentEl: '.user-profile'
+ action: @opts.action
+ )
diff --git a/app/assets/javascripts/user_tabs.js.coffee b/app/assets/javascripts/user_tabs.js.coffee
new file mode 100644
index 00000000000..09b7eec9104
--- /dev/null
+++ b/app/assets/javascripts/user_tabs.js.coffee
@@ -0,0 +1,146 @@
+# UserTabs
+#
+# Handles persisting and restoring the current tab selection and lazily-loading
+# content on the Users#show page.
+#
+# ### Example Markup
+#
+# <ul class="nav-links">
+# <li class="activity-tab active">
+# <a data-action="activity" data-target="#activity" data-toggle="tab" href="/u/username">
+# Activity
+# </a>
+# </li>
+# <li class="groups-tab">
+# <a data-action="groups" data-target="#groups" data-toggle="tab" href="/u/username/groups">
+# Groups
+# </a>
+# </li>
+# <li class="contributed-tab">
+# <a data-action="contributed" data-target="#contributed" data-toggle="tab" href="/u/username/contributed">
+# Contributed projects
+# </a>
+# </li>
+# <li class="projects-tab">
+# <a data-action="projects" data-target="#projects" data-toggle="tab" href="/u/username/projects">
+# Personal projects
+# </a>
+# </li>
+# </ul>
+#
+# <div class="tab-content">
+# <div class="tab-pane" id="activity">
+# Activity Content
+# </div>
+# <div class="tab-pane" id="groups">
+# Groups Content
+# </div>
+# <div class="tab-pane" id="contributed">
+# Contributed projects content
+# </div>
+# <div class="tab-pane" id="projects">
+# Projects content
+# </div>
+# </div>
+#
+# <div class="loading-status">
+# <div class="loading">
+# Loading Animation
+# </div>
+# </div>
+#
+class @UserTabs
+ constructor: (opts) ->
+ {
+ @action = 'activity'
+ @defaultAction = 'activity'
+ @parentEl = $(document)
+ } = opts
+
+ # Make jQuery object if selector is provided
+ @parentEl = $(@parentEl) if typeof @parentEl is 'string'
+
+ # Store the `location` object, allowing for easier stubbing in tests
+ @_location = location
+
+ # Set tab states
+ @loaded = {}
+ for item in @parentEl.find('.nav-links a')
+ @loaded[$(item).attr 'data-action'] = false
+
+ # Actions
+ @actions = Object.keys @loaded
+
+ @bindEvents()
+
+ # Set active tab
+ @action = @defaultAction if @action is 'show'
+ @activateTab(@action)
+
+ bindEvents: ->
+ # Toggle event listeners
+ @parentEl
+ .off 'shown.bs.tab', '.nav-links a[data-toggle="tab"]'
+ .on 'shown.bs.tab', '.nav-links a[data-toggle="tab"]', @tabShown
+
+ tabShown: (event) =>
+ $target = $(event.target)
+ action = $target.data('action')
+ source = $target.attr('href')
+
+ @setTab(source, action)
+ @setCurrentAction(action)
+
+ activateTab: (action) ->
+ @parentEl.find(".nav-links .#{action}-tab a").tab('show')
+
+ setTab: (source, action) ->
+ return if @loaded[action] is true
+
+ if action is 'activity'
+ @loadActivities(source)
+
+ if action in ['groups', 'contributed', 'projects']
+ @loadTab(source, action)
+
+ loadTab: (source, action) ->
+ $.ajax
+ beforeSend: => @toggleLoading(true)
+ complete: => @toggleLoading(false)
+ dataType: 'json'
+ type: 'GET'
+ url: "#{source}.json"
+ success: (data) =>
+ tabSelector = 'div#' + action
+ @parentEl.find(tabSelector).html(data.html)
+ @loaded[action] = true
+
+ loadActivities: (source) ->
+ return if @loaded['activity'] is true
+
+ $calendarWrap = @parentEl.find('.user-calendar')
+ $calendarWrap.load($calendarWrap.data('href'))
+
+ new Activities()
+ @loaded['activity'] = true
+
+ toggleLoading: (status) ->
+ @parentEl.find('.loading-status .loading').toggle(status)
+
+ setCurrentAction: (action) ->
+ # Remove possible actions from URL
+ regExp = new RegExp('\/(' + @actions.join('|') + ')(\.html)?\/?$')
+ new_state = @_location.pathname
+ new_state = new_state.replace(/\/+$/, "") # remove trailing slashes
+ new_state = new_state.replace(regExp, '')
+
+ # Append the new action if we're on a tab other than 'activity'
+ unless action == @defaultAction
+ new_state += "/#{action}"
+
+ # Ensure parameters and hash come along for the ride
+ new_state += @_location.search + @_location.hash
+
+ history.replaceState {turbolinks: true, url: new_state}, document.title, new_state
+
+ new_state
diff --git a/app/assets/javascripts/wikis.js.coffee b/app/assets/javascripts/wikis.js.coffee
index 19420f42468..1ee827f1fa3 100644
--- a/app/assets/javascripts/wikis.js.coffee
+++ b/app/assets/javascripts/wikis.js.coffee
@@ -2,7 +2,7 @@
class @Wikis
constructor: ->
- $('.build-new-wiki').bind 'click', (e) =>
+ $('.new-wiki-page').on 'submit', (e) =>
$('[data-error~=slug]').addClass('hidden')
field = $('#new_wiki_path')
slug = @slugify(field.val())
@@ -10,6 +10,7 @@ class @Wikis
if (slug.length > 0)
path = field.attr('data-wikis-path')
location.href = path + '/' + slug
+ e.preventDefault()
dasherize: (value) ->
value.replace(/[_\s]+/g, '-')
diff --git a/app/assets/stylesheets/application.scss b/app/assets/stylesheets/application.scss
index 0c0451fe4dd..e2d590f4df4 100644
--- a/app/assets/stylesheets/application.scss
+++ b/app/assets/stylesheets/application.scss
@@ -9,6 +9,7 @@
*= require_self
*= require dropzone/basic
*= require cal-heatmap
+ *= require cropper.css
*/
/*
@@ -25,12 +26,6 @@
@import "framework";
/*
- * NProgress load bar css
- */
-@import 'nprogress';
-@import 'nprogress-bootstrap';
-
-/*
* Font icons
*/
@import "font-awesome";
diff --git a/app/assets/stylesheets/framework.scss b/app/assets/stylesheets/framework.scss
index fa7641b1676..c85ab9148d0 100644
--- a/app/assets/stylesheets/framework.scss
+++ b/app/assets/stylesheets/framework.scss
@@ -11,6 +11,7 @@
@import "framework/calendar.scss";
@import "framework/callout.scss";
@import "framework/common.scss";
+@import "framework/dropdowns.scss";
@import "framework/files.scss";
@import "framework/filters.scss";
@import "framework/flash.scss";
@@ -26,6 +27,7 @@
@import "framework/mobile.scss";
@import "framework/nav.scss";
@import "framework/pagination.scss";
+@import "framework/progress.scss";
@import "framework/panels.scss";
@import "framework/selects.scss";
@import "framework/sidebar.scss";
diff --git a/app/assets/stylesheets/framework/blocks.scss b/app/assets/stylesheets/framework/blocks.scss
index bd89cc7dc1d..d7e4153ddc0 100644
--- a/app/assets/stylesheets/framework/blocks.scss
+++ b/app/assets/stylesheets/framework/blocks.scss
@@ -66,7 +66,7 @@
}
.oneline {
- line-height: 42px;
+ line-height: 35px;
}
> p:last-child {
diff --git a/app/assets/stylesheets/framework/buttons.scss b/app/assets/stylesheets/framework/buttons.scss
index 5f193fa7434..8d475137b03 100644
--- a/app/assets/stylesheets/framework/buttons.scss
+++ b/app/assets/stylesheets/framework/buttons.scss
@@ -7,7 +7,7 @@
&:focus,
&:active {
outline: none;
- @include box-shadow(inset 0 0 4px rgba(0, 0, 0, 0.12));
+ @include box-shadow($gl-btn-active-background);
}
}
@@ -28,7 +28,7 @@
}
&:active {
- @include box-shadow (inset 0 0 4px rgba(0, 0, 0, 0.12));
+ @include box-shadow ($gl-btn-active-background);
background-color: $dark;
border-color: $border-dark;
@@ -68,6 +68,12 @@
@include btn-default;
@include btn-white;
+ color: $gl-text-color;
+
+ &:focus:active {
+ outline: 0;
+ }
+
&.btn-small,
&.btn-sm {
padding: 4px 10px;
@@ -130,6 +136,24 @@
&.disabled {
pointer-events: auto !important;
}
+
+ .caret {
+ margin-left: 5px;
+ color: $gray-darkest;
+ }
+}
+
+.btn-transparent {
+ color: $btn-transparent-color;
+ background-color: transparent;
+ border: 0;
+
+ &:hover,
+ &:active,
+ &:focus {
+ background-color: transparent;
+ box-shadow: none;
+ }
}
.btn-block {
@@ -179,7 +203,7 @@
}
.active {
- @include box-shadow(inset 0 0 4px rgba(0, 0, 0, 0.12));
+ @include box-shadow($gl-btn-active-background);
border: 1px solid #c6cacf !important;
background-color: #e4e7ed !important;
diff --git a/app/assets/stylesheets/framework/common.scss b/app/assets/stylesheets/framework/common.scss
index ea56d9e12a0..c98e43ad09f 100644
--- a/app/assets/stylesheets/framework/common.scss
+++ b/app/assets/stylesheets/framework/common.scss
@@ -6,11 +6,15 @@
.cdark { color: #444 }
/** COMMON CLASSES **/
+.prepend-top-0 { margin-top: 0; }
+.prepend-top-5 { margin-top: 5px; }
.prepend-top-10 { margin-top:10px }
.prepend-top-default { margin-top: $gl-padding !important; }
.prepend-top-20 { margin-top:20px }
.prepend-left-10 { margin-left:10px }
+.prepend-left-default { margin-left:$gl-padding }
.prepend-left-20 { margin-left:20px }
+.append-right-5 { margin-right: 5px }
.append-right-10 { margin-right:10px }
.append-right-20 { margin-right:20px }
.append-bottom-10 { margin-bottom:10px }
@@ -56,21 +60,6 @@ hr {
margin: $gl-padding 0;
}
-.dropdown-menu > li > a {
- text-shadow: none;
-}
-
-.dropdown-menu-align-right {
- left: auto;
- right: 0px;
-}
-
-.dropdown-menu > li > a:hover,
-.dropdown-menu > li > a:focus {
- background: $gl-primary;
- color: #FFF;
-}
-
.str-truncated {
@include str-truncated;
}
@@ -301,7 +290,7 @@ table {
}
.btn-sign-in {
- margin-top: 8px;
+ margin-top: 10px;
text-shadow: none;
}
diff --git a/app/assets/stylesheets/framework/dropdowns.scss b/app/assets/stylesheets/framework/dropdowns.scss
new file mode 100644
index 00000000000..d4878b333f9
--- /dev/null
+++ b/app/assets/stylesheets/framework/dropdowns.scss
@@ -0,0 +1,102 @@
+.caret {
+ display: inline-block;
+ width: 0;
+ height: 0;
+ margin-left: 2px;
+ vertical-align: middle;
+ border-top: $caret-width-base dashed $dropdown-caret-color;
+ border-right: $caret-width-base solid transparent;
+ border-left: $caret-width-base solid transparent;
+}
+
+.dropdown {
+ position: relative;
+}
+
+.open {
+ .dropdown-menu {
+ display: block;
+ }
+}
+
+.dropdown-menu {
+ display: none;
+ position: absolute;
+ top: 100%;
+ left: 0;
+ z-index: 9999;
+ width: 240px;
+ margin-top: 2px;
+ margin-bottom: 0;
+ padding: 10px 10px;
+ font-size: 14px;
+ font-weight: normal;
+ background-color: $dropdown-bg;
+ border: 1px solid $dropdown-border-color;
+ border-radius: $border-radius-base;
+ box-shadow: 0 2px 4px $dropdown-shadow-color;
+
+ li {
+ text-align: left;
+ list-style: none;
+ }
+
+ .divider {
+ width: 100%;
+ height: 1px;
+ margin-top: 8px;
+ margin-bottom: 8px;
+ background-color: $dropdown-divider-color;
+ }
+
+ a {
+ display: block;
+ position: relative;
+ padding-left: 10px;
+ padding-right: 10px;
+ color: $dropdown-link-color;
+ line-height: 34px;
+ text-overflow: ellipsis;
+ border-radius: 2px;
+ white-space: nowrap;
+ overflow: hidden;
+
+ &:hover {
+ background-color: $dropdown-link-hover-bg;
+ text-decoration: none;
+ }
+ }
+}
+
+.dropdown-menu-align-right {
+ left: auto;
+ right: 0;
+}
+
+.dropdown-menu-selectable {
+ a {
+ padding-left: 25px;
+
+ &.is-active {
+ &::before {
+ content: "\f00c";
+ position: absolute;
+ left: 4px;
+ top: 8px;
+ font: normal normal normal 14px/1 FontAwesome;
+ font-size: inherit;
+ text-rendering: auto;
+ -webkit-font-smoothing: antialiased;
+ -moz-osx-font-smoothing: grayscale;
+ }
+ }
+ }
+}
+
+.dropdown-header {
+ padding-left: 10px;
+ padding-right: 10px;
+ color: $dropdown-header-color;
+ font-size: 13px;
+ line-height: 22px;
+}
diff --git a/app/assets/stylesheets/framework/files.scss b/app/assets/stylesheets/framework/files.scss
index c7f3604850d..07907e6e5a6 100644
--- a/app/assets/stylesheets/framework/files.scss
+++ b/app/assets/stylesheets/framework/files.scss
@@ -158,7 +158,7 @@
}
&:hover {
- background: $hover;
+ background: $row-hover;
}
}
}
diff --git a/app/assets/stylesheets/framework/filters.scss b/app/assets/stylesheets/framework/filters.scss
index b7638c86bfa..eab41628677 100644
--- a/app/assets/stylesheets/framework/filters.scss
+++ b/app/assets/stylesheets/framework/filters.scss
@@ -21,10 +21,3 @@
}
}
}
-
-.issues-filters,
-.issues_bulk_update {
- .select2-container .select2-choice {
- color: #444 !important;
- }
-}
diff --git a/app/assets/stylesheets/framework/forms.scss b/app/assets/stylesheets/framework/forms.scss
index d097e4d32f7..6c08005812e 100644
--- a/app/assets/stylesheets/framework/forms.scss
+++ b/app/assets/stylesheets/framework/forms.scss
@@ -28,15 +28,15 @@ input[type='search'].search-input {
}
&.search-input:-moz-placeholder { /* Firefox 18- */
- text-align: center;
+ text-align: center;
}
&.search-input::-moz-placeholder { /* Firefox 19+ */
- text-align: center;
+ text-align: center;
}
- &.search-input:-ms-input-placeholder {
- text-align: center;
+ &.search-input:-ms-input-placeholder {
+ text-align: center;
}
}
@@ -69,6 +69,10 @@ label {
&.inline-label {
margin: 0;
}
+
+ &.label-light {
+ font-weight: 600;
+ }
}
.inline-input-group {
diff --git a/app/assets/stylesheets/framework/gitlab-theme.scss b/app/assets/stylesheets/framework/gitlab-theme.scss
index 8d9a0aae568..12cef6f8ea1 100644
--- a/app/assets/stylesheets/framework/gitlab-theme.scss
+++ b/app/assets/stylesheets/framework/gitlab-theme.scss
@@ -117,4 +117,4 @@ body {
&.ui_violet {
@include gitlab-theme(#9988CC, $theme-violet, #443366, #332255);
}
-}
+} \ No newline at end of file
diff --git a/app/assets/stylesheets/framework/header.scss b/app/assets/stylesheets/framework/header.scss
index a81e258573d..e624982c5c9 100644
--- a/app/assets/stylesheets/framework/header.scss
+++ b/app/assets/stylesheets/framework/header.scss
@@ -77,6 +77,7 @@ header {
line-height: $header-height;
font-weight: normal;
color: #4c4e54;
+ overflow: hidden;
text-overflow: ellipsis;
vertical-align: top;
white-space: nowrap;
@@ -141,9 +142,13 @@ header {
}
@media (max-width: $screen-md-max) {
- .header-collapsed, .header-expanded {
- @include collapsed-header;
+ .header-collapsed {
+ margin-left: $sidebar_collapsed_width;
}
+
+ .header-expanded {
+ margin-left: $sidebar_width;
+ }
}
@media(min-width: $screen-md-max) {
diff --git a/app/assets/stylesheets/framework/highlight.scss b/app/assets/stylesheets/framework/highlight.scss
index 9854df4c45c..12e2f00fe89 100644
--- a/app/assets/stylesheets/framework/highlight.scss
+++ b/app/assets/stylesheets/framework/highlight.scss
@@ -44,8 +44,10 @@
white-space: nowrap;
i {
+ float: left;
+ margin-top: 3px;
+ margin-right: 5px;
visibility: hidden;
- @extend .pull-left;
}
&:hover i {
diff --git a/app/assets/stylesheets/framework/issue_box.scss b/app/assets/stylesheets/framework/issue_box.scss
index 08dcb563dce..77a00586b26 100644
--- a/app/assets/stylesheets/framework/issue_box.scss
+++ b/app/assets/stylesheets/framework/issue_box.scss
@@ -5,32 +5,38 @@
*/
.status-box {
- @include border-radius(3px);
+
+ /* Extra small devices (phones, less than 768px) */
+ /* No media query since this is the default in Bootstrap */
+ padding: 5px 11px;
+ margin-top: 4px;
+ /* Small devices (tablets, 768px and up) */
+ @media (min-width: $screen-sm-min) {
+ padding: 0 $gl-btn-padding;
+ margin-top: 5px;
+ }
+ @include border-radius(3px);
display: block;
float: left;
- padding: 0 $gl-btn-padding;
- font-weight: normal;
margin-right: 10px;
+ color: #FFF;
font-size: $gl-font-size;
+ line-height: 25px;
&.status-box-closed {
background-color: $gl-danger;
- color: #FFF;
}
&.status-box-merged {
background-color: $gl-primary;
- color: #FFF;
}
&.status-box-open {
background-color: $green-light;
- color: #FFF;
}
&.status-box-expired {
background: #cea61b;
- color: #FFF;
}
}
diff --git a/app/assets/stylesheets/framework/jquery.scss b/app/assets/stylesheets/framework/jquery.scss
index d6cd78813c0..0cdcd923b3c 100644
--- a/app/assets/stylesheets/framework/jquery.scss
+++ b/app/assets/stylesheets/framework/jquery.scss
@@ -48,8 +48,8 @@
.ui-state-hover,
.ui-state-focus {
- border: 1px solid $hover;
- background: $hover;
+ border: 1px solid $row-hover;
+ background: $row-hover;
color: #333;
}
}
diff --git a/app/assets/stylesheets/framework/lists.scss b/app/assets/stylesheets/framework/lists.scss
index 5c65383ec1a..bfec0911b3c 100644
--- a/app/assets/stylesheets/framework/lists.scss
+++ b/app/assets/stylesheets/framework/lists.scss
@@ -3,6 +3,7 @@
*
*/
.well-list {
+ position: relative;
margin: 0;
padding: 0;
list-style: none;
@@ -38,7 +39,7 @@
&.smoke { background-color: $background-color; }
&:hover {
- background: $hover;
+ background: $row-hover;
}
&:last-child {
@@ -110,7 +111,20 @@ ul.content-list {
> li {
border-color: $table-border-color;
- color: $gl-gray;
+ color: $list-text-color;
+ font-size: $list-font-size;
+
+ .title {
+ color: $list-title-color;
+ font-weight: 600;
+ }
+
+ .description {
+ p {
+ @include str-truncated;
+ margin-bottom: 0;
+ }
+ }
.avatar {
margin-right: 15px;
@@ -127,13 +141,6 @@ ul.content-list {
}
}
-.panel > .content-list {
- li {
- margin: 0;
- padding: $gl-padding;
- }
-}
-
ul.controls {
padding-top: 1px;
float: right;
diff --git a/app/assets/stylesheets/framework/mixins.scss b/app/assets/stylesheets/framework/mixins.scss
index 1d5000fe388..368bbfe5355 100644
--- a/app/assets/stylesheets/framework/mixins.scss
+++ b/app/assets/stylesheets/framework/mixins.scss
@@ -41,6 +41,12 @@
transition: $transition;
}
+@mixin transform($transform) {
+ -webkit-transform: $transform;
+ -ms-transform: $transform;
+ transform: $transform;
+}
+
/**
* Prefilled mixins
* Mixins with fixed values
diff --git a/app/assets/stylesheets/framework/nav.scss b/app/assets/stylesheets/framework/nav.scss
index 252a586358c..7de874c8bcd 100644
--- a/app/assets/stylesheets/framework/nav.scss
+++ b/app/assets/stylesheets/framework/nav.scss
@@ -77,12 +77,25 @@
margin-bottom: 0px;
> .dropdown {
- margin-right: 10px;
+ margin-right: $gl-padding-top;
display: inline-block;
+
+ &:last-child {
+ margin-right: 0;
+ }
}
> .btn {
+ margin-right: $gl-padding-top;
display: inline-block;
+
+ &:last-child {
+ margin-right: 0;
+ }
+ }
+
+ > .btn-grouped {
+ float: none;
}
> form {
@@ -94,7 +107,7 @@
display: inline-block;
position: relative;
top: 1px;
- margin-right: 10px;
+ margin-right: $gl-padding-top;
/* Medium devices (desktops, 992px and up) */
@media (min-width: $screen-md-min) { width: 200px; }
diff --git a/app/assets/stylesheets/framework/progress.scss b/app/assets/stylesheets/framework/progress.scss
new file mode 100644
index 00000000000..e9800bd24b5
--- /dev/null
+++ b/app/assets/stylesheets/framework/progress.scss
@@ -0,0 +1,5 @@
+html.turbolinks-progress-bar::before {
+ background-color: $progress-color!important;
+ height: 2px!important;
+ box-shadow: 0 0 10px $progress-color, 0 0 5px $progress-color;
+}
diff --git a/app/assets/stylesheets/framework/selects.scss b/app/assets/stylesheets/framework/selects.scss
index 3ee3443e349..7bf04e4ad74 100644
--- a/app/assets/stylesheets/framework/selects.scss
+++ b/app/assets/stylesheets/framework/selects.scss
@@ -1,49 +1,53 @@
/** Select2 selectbox style override **/
+.select2-container {
+ width: 100% !important;
+}
+
.select2-container, .select2-container.select2-drop-above {
.select2-choice {
- background: #FFF;
- border-color: #DDD;
- height: 36px;
- padding: 6px $gl-padding;
+ background: #fff;
+ border-color: $input-border;
+ border-color: $border-white-light;
+ height: 35px;
+ padding: $gl-vert-padding $gl-btn-padding;
font-size: $gl-font-size;
line-height: 1.42857143;
- @include border-radius(2px);
+ @include border-radius($border-radius-default);
.select2-arrow {
- background: #FFF;
- border-left: none;
- padding-top: 5px;
+ background-image: none;
+ background-color: transparent;
+ border: none;
+ padding-top: 6px;
+ padding-right: 10px;
+
+ b {
+ @extend .caret;
+ color: $gray-darkest;
+ }
}
.select2-chosen {
- color: $gl-text-color;
+ margin-right: 15px;
}
- &.select2-default {
- .select2-chosen {
- color: #999;
- }
+ &:hover {
+ background-color: $gray-dark;
+ border-color: $border-white-normal;
+ color: $gl-text-color;
}
}
}
-.select2-container .select2-choice, .select2-container.select2-drop-above .select2-choice{
- color: #7f8fa4;
- border: 1px solid #e7e9ed;
-}
-
-
.select2-drop {
@include box-shadow(rgba(76, 86, 103, 0.247059) 0px 0px 1px 0px, rgba(31, 37, 50, 0.317647) 0px 2px 18px 0px);
- @include border-radius (0px);
-
- padding: 16px;
- border: none !important;
+ @include border-radius ($border-radius-default);
+ border: none;
}
.select2-results .select2-result-label {
- padding: 9px;
+ padding: 10px 15px;
}
.select2-drop{
@@ -56,15 +60,30 @@
.select2-results li.select2-result-with-children > .select2-result-label {
font-weight: 600;
- color: #313236;
+ color: $gl-text-color;
+}
+
+.select2-container-active {
+ .select2-choice, .select2-choices {
+ @include box-shadow(none);
+ }
+}
+
+.select2-dropdown-open {
+ .select2-choice {
+ border-color: $border-white-normal;
+ outline: 0;
+ background-image: none;
+ background-color: $white-dark;
+ @include box-shadow($gl-btn-active-gradient);
+ }
}
.select2-container-multi {
.select2-choices {
- @include border-radius(2px);
+ @include border-radius($border-radius-default);
border-color: $input-border;
- background: white;
- padding-left: $gl-padding / 2;
+ background: none;
.select2-search-field input {
padding: $gl-padding / 2;
@@ -76,14 +95,16 @@
.select2-search-choice {
margin: 8px 0 0 8px;
- background: white;
box-shadow: none;
border-color: $input-border;
color: $gl-text-color;
line-height: 15px;
+ background-color: $background-color;
+ background-image: none;
.select2-search-choice-close {
- top: 5px;
+ top: 4px;
+ left: 3px;
}
&.select2-search-choice-focus {
@@ -91,22 +112,25 @@
}
}
}
+
+ &.select2-container-active .select2-choices,
+ &.select2-dropdown-open .select2-choices {
+ border-color: $border-white-normal;
+ @include box-shadow($gl-btn-active-gradient);
+ }
+}
+
+.select2-container-multi .select2-choices .select2-search-choice {
}
.select2-drop-active {
- border: 1px solid #BBB !important;
- margin-top: 4px;
- font-size: 13px;
+ margin-top: 6px;
+ font-size: 14px;
&.select2-drop-above {
margin-bottom: 8px;
}
- .select2-search input {
- background: #fafafa;
- border-color: #DDD;
- }
-
.select2-results {
max-height: 350px;
.select2-highlighted {
@@ -115,8 +139,34 @@
}
}
-.select2-container {
- width: 100% !important;
+.select2-search {
+ padding: 15px 15px 5px;
+
+ .select2-drop-auto-width & {
+ padding: 15px 15px 5px;
+ }
+}
+
+.select2-search input {
+ padding: 2px 25px 2px 5px;
+ background: #fff image-url('select2.png');
+ background-repeat: no-repeat;
+ background-position: right 0px bottom 6px;
+ border: 1px solid $input-border;
+ @include border-radius($border-radius-default);
+ @include transition(border-color ease-in-out .15s, box-shadow ease-in-out .15s);
+
+ &:focus {
+ border-color: $input-border-focus;
+ }
+}
+
+.select2-search input.select2-active {
+ background-color: #fff;
+ background-image: image-url('select2-spinner.gif') !important;
+ background-repeat: no-repeat;
+ background-position: right 5px center !important;
+ background-size: 16px 16px !important;
}
/** Branch/tag selector **/
@@ -124,10 +174,19 @@
width: 160px !important;
}
-.ajax-users-dropdown, .ajax-project-users-dropdown {
- .select2-search {
- padding-top: 2px;
- }
+.select2-results .select2-no-results,
+.select2-results .select2-searching,
+.select2-results .select2-ajax-error,
+.select2-results .select2-selection-limit {
+ background: $gray-light;
+ display: list-item;
+ padding: 10px 15px;
+}
+
+
+.select2-results {
+ margin: 0;
+ padding: 10px 0;
}
.ajax-users-select {
diff --git a/app/assets/stylesheets/framework/sidebar.scss b/app/assets/stylesheets/framework/sidebar.scss
index b141928f706..6b382e4b1b2 100644
--- a/app/assets/stylesheets/framework/sidebar.scss
+++ b/app/assets/stylesheets/framework/sidebar.scss
@@ -12,6 +12,30 @@
height: 100%;
transition-duration: .3s;
}
+
+ .gitlab-text-container-link {
+ z-index: 1;
+ position: absolute;
+ left: 0px;
+ }
+
+ #logo {
+ z-index: 2;
+ position: absolute;
+ width: 58px;
+ cursor: pointer;
+ }
+
+ &.right-sidebar-expanded {
+ /* Extra small devices (phones, less than 768px) */
+ /* No media query since this is the default in Bootstrap */
+ padding-right: 0;
+ /* Small devices (tablets, 768px and up) */
+ @media (min-width: $screen-sm-min) {
+ padding-right: $gutter_width;
+ }
+
+ }
}
.sidebar-wrapper {
@@ -45,19 +69,6 @@
overflow: hidden;
transition-duration: .3s;
- .home {
- z-index: 1;
- position: absolute;
- left: 0px;
- }
-
- #logo {
- z-index: 2;
- position: absolute;
- width: 58px;
- cursor: pointer;
- }
-
a {
float: left;
height: $header-height;
@@ -194,6 +205,15 @@
@mixin expanded-sidebar {
padding-left: $sidebar_width;
+ &.right-sidebar-collapsed {
+ /* Extra small devices (phones, less than 768px) */
+ padding-right: 0;
+ /* Small devices (tablets, 768px and up) */
+ @media (min-width: $screen-sm-min) {
+ padding-right: $sidebar_collapsed_width;
+ }
+ }
+
.sidebar-wrapper {
width: $sidebar_width;
@@ -213,17 +233,18 @@
}
}
-@mixin expanded-gutter {
- padding-right: $gutter_width;
-}
-
-@mixin collapsed-gutter {
- padding-right: $sidebar_collapsed_width;
-}
-
@mixin collapsed-sidebar {
padding-left: $sidebar_collapsed_width;
+ &.right-sidebar-collapsed {
+ /* Extra small devices (phones, less than 768px) */
+ padding-right: 0;
+ /* Small devices (tablets, 768px and up) */
+ @media (min-width: $screen-sm-min) {
+ padding-right: $sidebar_collapsed_width;
+ }
+ }
+
.sidebar-wrapper {
width: $sidebar_collapsed_width;
@@ -287,47 +308,16 @@
background: #f2f6f7;
}
-// page is small enough
-@media (max-width: $screen-md-max) {
- .page-sidebar-collapsed {
+.page-sidebar-collapsed {
+ /* Extra small devices (phones, less than 768px) */
+ @include collapsed-sidebar;
+ padding-right: 0;
+ /* Small devices (tablets, 768px and up) */
+ @media (min-width: $screen-sm-min) {
@include collapsed-sidebar;
}
-
- .page-sidebar-expanded {
- @include collapsed-sidebar;
- }
-
- .page-gutter {
- &.right-sidebar-collapsed {
- @include collapsed-gutter;
- }
- &.right-sidebar-expanded {
- @include expanded-gutter;
- }
- }
-
- .collapse-nav {
- display: none;
- }
}
-// page is large enough
-@media(min-width: $screen-md-max) {
-
- .page-gutter {
- &.right-sidebar-collapsed {
- @include collapsed-gutter;
- }
- &.right-sidebar-expanded {
- @include expanded-gutter;
- }
- }
-
- .page-sidebar-collapsed {
- @include collapsed-sidebar;
- }
-
- .page-sidebar-expanded {
- @include expanded-sidebar;
- }
-} \ No newline at end of file
+.page-sidebar-expanded {
+ @include expanded-sidebar;
+}
diff --git a/app/assets/stylesheets/framework/timeline.scss b/app/assets/stylesheets/framework/timeline.scss
index 47b843e5e3d..aa244fe548d 100644
--- a/app/assets/stylesheets/framework/timeline.scss
+++ b/app/assets/stylesheets/framework/timeline.scss
@@ -5,13 +5,13 @@
padding: 0;
.timeline-entry {
- padding: $gl-padding 0;
+ padding: $gl-padding $gl-btn-padding;
border-color: $table-border-color;
color: $gl-gray;
border-bottom: 1px solid $border-white-light;
&:target {
- background: $hover;
+ background: $row-hover;
}
&:last-child {
diff --git a/app/assets/stylesheets/framework/tw_bootstrap.scss b/app/assets/stylesheets/framework/tw_bootstrap.scss
index 3e709244879..ddf76704a53 100644
--- a/app/assets/stylesheets/framework/tw_bootstrap.scss
+++ b/app/assets/stylesheets/framework/tw_bootstrap.scss
@@ -22,7 +22,7 @@
// Components
@import "bootstrap/component-animations";
-@import "bootstrap/dropdowns";
+// @import "bootstrap/dropdowns";
@import "bootstrap/button-groups";
@import "bootstrap/input-groups";
@import "bootstrap/navs";
@@ -167,12 +167,6 @@
}
}
-.alert-help {
- background-color: $background-color;
- border: 1px solid $border-color;
- color: $gl-gray;
-}
-
// Typography =================================================================
.text-primary,
diff --git a/app/assets/stylesheets/framework/tw_bootstrap_variables.scss b/app/assets/stylesheets/framework/tw_bootstrap_variables.scss
index 33270388e64..b1b8295411b 100644
--- a/app/assets/stylesheets/framework/tw_bootstrap_variables.scss
+++ b/app/assets/stylesheets/framework/tw_bootstrap_variables.scss
@@ -70,7 +70,7 @@ $pagination-bg: #fff;
$pagination-border: $border-color;
$pagination-hover-color: $gl-gray;
-$pagination-hover-bg: $hover;
+$pagination-hover-bg: $row-hover;
$pagination-hover-border: $border-color;
$pagination-active-color: $blue-dark;
diff --git a/app/assets/stylesheets/framework/typography.scss b/app/assets/stylesheets/framework/typography.scss
index 8d8f41287da..48570abff49 100644
--- a/app/assets/stylesheets/framework/typography.scss
+++ b/app/assets/stylesheets/framework/typography.scss
@@ -196,7 +196,7 @@ body {
h1, h2, h3, h4, h5, h6 {
color: $gl-header-color;
- font-weight: 500;
+ font-weight: 600;
}
/** CODE **/
diff --git a/app/assets/stylesheets/framework/variables.scss b/app/assets/stylesheets/framework/variables.scss
index b8386362637..cc84a5ff932 100644
--- a/app/assets/stylesheets/framework/variables.scss
+++ b/app/assets/stylesheets/framework/variables.scss
@@ -1,4 +1,4 @@
-$hover: #faf9f9;
+$row-hover: #f4f8fe;
$gl-text-color: #54565B;
$gl-text-green: #4A2;
$gl-text-red: #D12F19;
@@ -7,7 +7,7 @@ $gl-header-color: #323232;
$gl-link-color: #333c48;
$md-text-color: #444;
$md-link-color: #3084bb;
-$nprogress-color: #c0392b;
+$progress-color: #c0392b;
$gl-font-size: 15px;
$list-font-size: 15px;
$sidebar_collapsed_width: 62px;
@@ -31,6 +31,15 @@ $gl-padding-top:10px;
$gl-avatar-size: 40px;
$secondary-text: #7f8fa4;
$error-exclamation-point: #E62958;
+$border-radius-default: 3px;
+$list-title-color: #333333;
+$list-text-color: #555555;
+$profile-settings-link-color: $md-link-color;
+
+$btn-transparent-color: #8F8F8F;
+
+$ssh-key-icon-color: #8F8F8F;
+$ssh-key-icon-size: 18px;
/*
* Color schema
@@ -89,6 +98,9 @@ $border-red-light: #E52C5A;
$border-red-normal: #D22852;
$border-red-dark: #CA264F;
+$help-well-bg: #FAFAFA;
+$help-well-border: #E5E5E5;
+
/* header */
$light-grey-header: #faf9f9;
@@ -100,6 +112,8 @@ $gl-success: $green-normal;
$gl-info: $blue-normal;
$gl-warning: $orange-normal;
$gl-danger: $red-normal;
+$gl-btn-active-background: rgba(0, 0, 0, 0.12);
+$gl-btn-active-gradient: inset 0 0 4px $gl-btn-active-background;
/*
* Commit Diff Colors
@@ -112,3 +126,15 @@ $deleted: #f77;
*/
$monospace_font: 'Menlo', 'Liberation Mono', 'Consolas', 'DejaVu Sans Mono', 'Ubuntu Mono', 'Courier New', 'andale mono', 'lucida console', monospace;
$regular_font: 'Source Sans Pro', "Helvetica Neue", Helvetica, Arial, sans-serif;
+
+/*
+* Dropdowns
+*/
+$dropdown-bg: #fff;
+$dropdown-link-color: #555;
+$dropdown-link-hover-bg: rgba(#000, .04);
+$dropdown-border-color: rgba(#000, .1);
+$dropdown-shadow-color: rgba(#000, .1);
+$dropdown-divider-color: rgba(#000, .1);
+$dropdown-header-color: #959494;
+$dropdown-caret-color: #54565B;
diff --git a/app/assets/stylesheets/pages/appearances.scss b/app/assets/stylesheets/pages/appearances.scss
new file mode 100644
index 00000000000..e2070f17c3b
--- /dev/null
+++ b/app/assets/stylesheets/pages/appearances.scss
@@ -0,0 +1,11 @@
+.appearance-logo-preview {
+ max-width: 400px;
+ margin-bottom: 20px;
+}
+
+.appearance-light-logo-preview {
+ background-color: $background-color;
+ max-width: 72px;
+ padding: 10px;
+ margin-bottom: 10px;
+}
diff --git a/app/assets/stylesheets/pages/detail_page.scss b/app/assets/stylesheets/pages/detail_page.scss
index 529a43548c8..d3eda1a57e6 100644
--- a/app/assets/stylesheets/pages/detail_page.scss
+++ b/app/assets/stylesheets/pages/detail_page.scss
@@ -12,6 +12,15 @@
.identifier {
color: #5c5d5e;
}
+
+ .issue_created_ago, .author_link {
+ white-space: nowrap;
+ }
+
+ .issue-meta {
+ display: inline-block;
+ line-height: 20px;
+ }
}
.detail-page-description {
diff --git a/app/assets/stylesheets/pages/emojis.scss b/app/assets/stylesheets/pages/emojis.scss
index 89a94c5a780..6c721b514f8 100644
--- a/app/assets/stylesheets/pages/emojis.scss
+++ b/app/assets/stylesheets/pages/emojis.scss
@@ -1,1272 +1,1736 @@
-/*
-File is generated by https://github.com/jakesgordon/sprite-factory and midified manualy
-The source: gemojione gem.
-*/
+.emoji-0023-20E3 { background-position: 0px 0px; }
+.emoji-002A-20E3 { background-position: -20px 0px; }
+.emoji-0030-20E3 { background-position: 0px -20px; }
+.emoji-0031-20E3 { background-position: -20px -20px; }
+.emoji-0032-20E3 { background-position: -40px 0px; }
+.emoji-0033-20E3 { background-position: -40px -20px; }
+.emoji-0034-20E3 { background-position: 0px -40px; }
+.emoji-0035-20E3 { background-position: -20px -40px; }
+.emoji-0036-20E3 { background-position: -40px -40px; }
+.emoji-0037-20E3 { background-position: -60px 0px; }
+.emoji-0038-20E3 { background-position: -60px -20px; }
+.emoji-0039-20E3 { background-position: -60px -40px; }
+.emoji-00A9 { background-position: 0px -60px; }
+.emoji-00AE { background-position: -20px -60px; }
+.emoji-1F004 { background-position: -40px -60px; }
+.emoji-1F0CF { background-position: -60px -60px; }
+.emoji-1F170 { background-position: -80px 0px; }
+.emoji-1F171 { background-position: -80px -20px; }
+.emoji-1F17E { background-position: -80px -40px; }
+.emoji-1F17F { background-position: -80px -60px; }
+.emoji-1F18E { background-position: 0px -80px; }
+.emoji-1F191 { background-position: -20px -80px; }
+.emoji-1F192 { background-position: -40px -80px; }
+.emoji-1F193 { background-position: -60px -80px; }
+.emoji-1F194 { background-position: -80px -80px; }
+.emoji-1F195 { background-position: -100px 0px; }
+.emoji-1F196 { background-position: -100px -20px; }
+.emoji-1F197 { background-position: -100px -40px; }
+.emoji-1F198 { background-position: -100px -60px; }
+.emoji-1F199 { background-position: -100px -80px; }
+.emoji-1F19A { background-position: 0px -100px; }
+.emoji-1F1E6-1F1E8 { background-position: -20px -100px; }
+.emoji-1F1E6-1F1E9 { background-position: -40px -100px; }
+.emoji-1F1E6-1F1EA { background-position: -60px -100px; }
+.emoji-1F1E6-1F1EB { background-position: -80px -100px; }
+.emoji-1F1E6-1F1EC { background-position: -100px -100px; }
+.emoji-1F1E6-1F1EE { background-position: -120px 0px; }
+.emoji-1F1E6-1F1F1 { background-position: -120px -20px; }
+.emoji-1F1E6-1F1F2 { background-position: -120px -40px; }
+.emoji-1F1E6-1F1F4 { background-position: -120px -60px; }
+.emoji-1F1E6-1F1F6 { background-position: -120px -80px; }
+.emoji-1F1E6-1F1F7 { background-position: -120px -100px; }
+.emoji-1F1E6-1F1F8 { background-position: 0px -120px; }
+.emoji-1F1E6-1F1F9 { background-position: -20px -120px; }
+.emoji-1F1E6-1F1FA { background-position: -40px -120px; }
+.emoji-1F1E6-1F1FC { background-position: -60px -120px; }
+.emoji-1F1E6-1F1FD { background-position: -80px -120px; }
+.emoji-1F1E6-1F1FF { background-position: -100px -120px; }
+.emoji-1F1E7-1F1E6 { background-position: -120px -120px; }
+.emoji-1F1E7-1F1E7 { background-position: -140px 0px; }
+.emoji-1F1E7-1F1E9 { background-position: -140px -20px; }
+.emoji-1F1E7-1F1EA { background-position: -140px -40px; }
+.emoji-1F1E7-1F1EB { background-position: -140px -60px; }
+.emoji-1F1E7-1F1EC { background-position: -140px -80px; }
+.emoji-1F1E7-1F1ED { background-position: -140px -100px; }
+.emoji-1F1E7-1F1EE { background-position: -140px -120px; }
+.emoji-1F1E7-1F1EF { background-position: 0px -140px; }
+.emoji-1F1E7-1F1F1 { background-position: -20px -140px; }
+.emoji-1F1E7-1F1F2 { background-position: -40px -140px; }
+.emoji-1F1E7-1F1F3 { background-position: -60px -140px; }
+.emoji-1F1E7-1F1F4 { background-position: -80px -140px; }
+.emoji-1F1E7-1F1F6 { background-position: -100px -140px; }
+.emoji-1F1E7-1F1F7 { background-position: -120px -140px; }
+.emoji-1F1E7-1F1F8 { background-position: -140px -140px; }
+.emoji-1F1E7-1F1F9 { background-position: -160px 0px; }
+.emoji-1F1E7-1F1FB { background-position: -160px -20px; }
+.emoji-1F1E7-1F1FC { background-position: -160px -40px; }
+.emoji-1F1E7-1F1FE { background-position: -160px -60px; }
+.emoji-1F1E7-1F1FF { background-position: -160px -80px; }
+.emoji-1F1E8-1F1E6 { background-position: -160px -100px; }
+.emoji-1F1E8-1F1E8 { background-position: -160px -120px; }
+.emoji-1F1E8-1F1E9 { background-position: -160px -140px; }
+.emoji-1F1E8-1F1EB { background-position: 0px -160px; }
+.emoji-1F1E8-1F1EC { background-position: -20px -160px; }
+.emoji-1F1E8-1F1ED { background-position: -40px -160px; }
+.emoji-1F1E8-1F1EE { background-position: -60px -160px; }
+.emoji-1F1E8-1F1F0 { background-position: -80px -160px; }
+.emoji-1F1E8-1F1F1 { background-position: -100px -160px; }
+.emoji-1F1E8-1F1F2 { background-position: -120px -160px; }
+.emoji-1F1E8-1F1F3 { background-position: -140px -160px; }
+.emoji-1F1E8-1F1F4 { background-position: -160px -160px; }
+.emoji-1F1E8-1F1F5 { background-position: -180px 0px; }
+.emoji-1F1E8-1F1F7 { background-position: -180px -20px; }
+.emoji-1F1E8-1F1FA { background-position: -180px -40px; }
+.emoji-1F1E8-1F1FB { background-position: -180px -60px; }
+.emoji-1F1E8-1F1FC { background-position: -180px -80px; }
+.emoji-1F1E8-1F1FD { background-position: -180px -100px; }
+.emoji-1F1E8-1F1FE { background-position: -180px -120px; }
+.emoji-1F1E8-1F1FF { background-position: -180px -140px; }
+.emoji-1F1E9-1F1EA { background-position: -180px -160px; }
+.emoji-1F1E9-1F1EC { background-position: 0px -180px; }
+.emoji-1F1E9-1F1EF { background-position: -20px -180px; }
+.emoji-1F1E9-1F1F0 { background-position: -40px -180px; }
+.emoji-1F1E9-1F1F2 { background-position: -60px -180px; }
+.emoji-1F1E9-1F1F4 { background-position: -80px -180px; }
+.emoji-1F1E9-1F1FF { background-position: -100px -180px; }
+.emoji-1F1EA-1F1E6 { background-position: -120px -180px; }
+.emoji-1F1EA-1F1E8 { background-position: -140px -180px; }
+.emoji-1F1EA-1F1EA { background-position: -160px -180px; }
+.emoji-1F1EA-1F1EC { background-position: -180px -180px; }
+.emoji-1F1EA-1F1ED { background-position: -200px 0px; }
+.emoji-1F1EA-1F1F7 { background-position: -200px -20px; }
+.emoji-1F1EA-1F1F8 { background-position: -200px -40px; }
+.emoji-1F1EA-1F1F9 { background-position: -200px -60px; }
+.emoji-1F1EA-1F1FA { background-position: -200px -80px; }
+.emoji-1F1EB-1F1EE { background-position: -200px -100px; }
+.emoji-1F1EB-1F1EF { background-position: -200px -120px; }
+.emoji-1F1EB-1F1F0 { background-position: -200px -140px; }
+.emoji-1F1EB-1F1F2 { background-position: -200px -160px; }
+.emoji-1F1EB-1F1F4 { background-position: -200px -180px; }
+.emoji-1F1EB-1F1F7 { background-position: 0px -200px; }
+.emoji-1F1EC-1F1E6 { background-position: -20px -200px; }
+.emoji-1F1EC-1F1E7 { background-position: -40px -200px; }
+.emoji-1F1EC-1F1E9 { background-position: -60px -200px; }
+.emoji-1F1EC-1F1EA { background-position: -80px -200px; }
+.emoji-1F1EC-1F1EB { background-position: -100px -200px; }
+.emoji-1F1EC-1F1EC { background-position: -120px -200px; }
+.emoji-1F1EC-1F1ED { background-position: -140px -200px; }
+.emoji-1F1EC-1F1EE { background-position: -160px -200px; }
+.emoji-1F1EC-1F1F1 { background-position: -180px -200px; }
+.emoji-1F1EC-1F1F2 { background-position: -200px -200px; }
+.emoji-1F1EC-1F1F3 { background-position: -220px 0px; }
+.emoji-1F1EC-1F1F5 { background-position: -220px -20px; }
+.emoji-1F1EC-1F1F6 { background-position: -220px -40px; }
+.emoji-1F1EC-1F1F7 { background-position: -220px -60px; }
+.emoji-1F1EC-1F1F8 { background-position: -220px -80px; }
+.emoji-1F1EC-1F1F9 { background-position: -220px -100px; }
+.emoji-1F1EC-1F1FA { background-position: -220px -120px; }
+.emoji-1F1EC-1F1FC { background-position: -220px -140px; }
+.emoji-1F1EC-1F1FE { background-position: -220px -160px; }
+.emoji-1F1ED-1F1F0 { background-position: -220px -180px; }
+.emoji-1F1ED-1F1F2 { background-position: -220px -200px; }
+.emoji-1F1ED-1F1F3 { background-position: 0px -220px; }
+.emoji-1F1ED-1F1F7 { background-position: -20px -220px; }
+.emoji-1F1ED-1F1F9 { background-position: -40px -220px; }
+.emoji-1F1ED-1F1FA { background-position: -60px -220px; }
+.emoji-1F1EE-1F1E8 { background-position: -80px -220px; }
+.emoji-1F1EE-1F1E9 { background-position: -100px -220px; }
+.emoji-1F1EE-1F1EA { background-position: -120px -220px; }
+.emoji-1F1EE-1F1F1 { background-position: -140px -220px; }
+.emoji-1F1EE-1F1F2 { background-position: -160px -220px; }
+.emoji-1F1EE-1F1F3 { background-position: -180px -220px; }
+.emoji-1F1EE-1F1F4 { background-position: -200px -220px; }
+.emoji-1F1EE-1F1F6 { background-position: -220px -220px; }
+.emoji-1F1EE-1F1F7 { background-position: -240px 0px; }
+.emoji-1F1EE-1F1F8 { background-position: -240px -20px; }
+.emoji-1F1EE-1F1F9 { background-position: -240px -40px; }
+.emoji-1F1EF-1F1EA { background-position: -240px -60px; }
+.emoji-1F1EF-1F1F2 { background-position: -240px -80px; }
+.emoji-1F1EF-1F1F4 { background-position: -240px -100px; }
+.emoji-1F1EF-1F1F5 { background-position: -240px -120px; }
+.emoji-1F1F0-1F1EA { background-position: -240px -140px; }
+.emoji-1F1F0-1F1EC { background-position: -240px -160px; }
+.emoji-1F1F0-1F1ED { background-position: -240px -180px; }
+.emoji-1F1F0-1F1EE { background-position: -240px -200px; }
+.emoji-1F1F0-1F1F2 { background-position: -240px -220px; }
+.emoji-1F1F0-1F1F3 { background-position: 0px -240px; }
+.emoji-1F1F0-1F1F5 { background-position: -20px -240px; }
+.emoji-1F1F0-1F1F7 { background-position: -40px -240px; }
+.emoji-1F1F0-1F1FC { background-position: -60px -240px; }
+.emoji-1F1F0-1F1FE { background-position: -80px -240px; }
+.emoji-1F1F0-1F1FF { background-position: -100px -240px; }
+.emoji-1F1F1-1F1E6 { background-position: -120px -240px; }
+.emoji-1F1F1-1F1E7 { background-position: -140px -240px; }
+.emoji-1F1F1-1F1E8 { background-position: -160px -240px; }
+.emoji-1F1F1-1F1EE { background-position: -180px -240px; }
+.emoji-1F1F1-1F1F0 { background-position: -200px -240px; }
+.emoji-1F1F1-1F1F7 { background-position: -220px -240px; }
+.emoji-1F1F1-1F1F8 { background-position: -240px -240px; }
+.emoji-1F1F1-1F1F9 { background-position: -260px 0px; }
+.emoji-1F1F1-1F1FA { background-position: -260px -20px; }
+.emoji-1F1F1-1F1FB { background-position: -260px -40px; }
+.emoji-1F1F1-1F1FE { background-position: -260px -60px; }
+.emoji-1F1F2-1F1E6 { background-position: -260px -80px; }
+.emoji-1F1F2-1F1E8 { background-position: -260px -100px; }
+.emoji-1F1F2-1F1E9 { background-position: -260px -120px; }
+.emoji-1F1F2-1F1EA { background-position: -260px -140px; }
+.emoji-1F1F2-1F1EB { background-position: -260px -160px; }
+.emoji-1F1F2-1F1EC { background-position: -260px -180px; }
+.emoji-1F1F2-1F1ED { background-position: -260px -200px; }
+.emoji-1F1F2-1F1F0 { background-position: -260px -220px; }
+.emoji-1F1F2-1F1F1 { background-position: -260px -240px; }
+.emoji-1F1F2-1F1F2 { background-position: 0px -260px; }
+.emoji-1F1F2-1F1F3 { background-position: -20px -260px; }
+.emoji-1F1F2-1F1F4 { background-position: -40px -260px; }
+.emoji-1F1F2-1F1F5 { background-position: -60px -260px; }
+.emoji-1F1F2-1F1F6 { background-position: -80px -260px; }
+.emoji-1F1F2-1F1F7 { background-position: -100px -260px; }
+.emoji-1F1F2-1F1F8 { background-position: -120px -260px; }
+.emoji-1F1F2-1F1F9 { background-position: -140px -260px; }
+.emoji-1F1F2-1F1FA { background-position: -160px -260px; }
+.emoji-1F1F2-1F1FB { background-position: -180px -260px; }
+.emoji-1F1F2-1F1FC { background-position: -200px -260px; }
+.emoji-1F1F2-1F1FD { background-position: -220px -260px; }
+.emoji-1F1F2-1F1FE { background-position: -240px -260px; }
+.emoji-1F1F2-1F1FF { background-position: -260px -260px; }
+.emoji-1F1F3-1F1E6 { background-position: -280px 0px; }
+.emoji-1F1F3-1F1E8 { background-position: -280px -20px; }
+.emoji-1F1F3-1F1EA { background-position: -280px -40px; }
+.emoji-1F1F3-1F1EB { background-position: -280px -60px; }
+.emoji-1F1F3-1F1EC { background-position: -280px -80px; }
+.emoji-1F1F3-1F1EE { background-position: -280px -100px; }
+.emoji-1F1F3-1F1F1 { background-position: -280px -120px; }
+.emoji-1F1F3-1F1F4 { background-position: -280px -140px; }
+.emoji-1F1F3-1F1F5 { background-position: -280px -160px; }
+.emoji-1F1F3-1F1F7 { background-position: -280px -180px; }
+.emoji-1F1F3-1F1FA { background-position: -280px -200px; }
+.emoji-1F1F3-1F1FF { background-position: -280px -220px; }
+.emoji-1F1F4-1F1F2 { background-position: -280px -240px; }
+.emoji-1F1F5-1F1E6 { background-position: -280px -260px; }
+.emoji-1F1F5-1F1EA { background-position: 0px -280px; }
+.emoji-1F1F5-1F1EB { background-position: -20px -280px; }
+.emoji-1F1F5-1F1EC { background-position: -40px -280px; }
+.emoji-1F1F5-1F1ED { background-position: -60px -280px; }
+.emoji-1F1F5-1F1F0 { background-position: -80px -280px; }
+.emoji-1F1F5-1F1F1 { background-position: -100px -280px; }
+.emoji-1F1F5-1F1F2 { background-position: -120px -280px; }
+.emoji-1F1F5-1F1F3 { background-position: -140px -280px; }
+.emoji-1F1F5-1F1F7 { background-position: -160px -280px; }
+.emoji-1F1F5-1F1F8 { background-position: -180px -280px; }
+.emoji-1F1F5-1F1F9 { background-position: -200px -280px; }
+.emoji-1F1F5-1F1FC { background-position: -220px -280px; }
+.emoji-1F1F5-1F1FE { background-position: -240px -280px; }
+.emoji-1F1F6-1F1E6 { background-position: -260px -280px; }
+.emoji-1F1F7-1F1EA { background-position: -280px -280px; }
+.emoji-1F1F7-1F1F4 { background-position: -300px 0px; }
+.emoji-1F1F7-1F1F8 { background-position: -300px -20px; }
+.emoji-1F1F7-1F1FA { background-position: -300px -40px; }
+.emoji-1F1F7-1F1FC { background-position: -300px -60px; }
+.emoji-1F1F8-1F1E6 { background-position: -300px -80px; }
+.emoji-1F1F8-1F1E7 { background-position: -300px -100px; }
+.emoji-1F1F8-1F1E8 { background-position: -300px -120px; }
+.emoji-1F1F8-1F1E9 { background-position: -300px -140px; }
+.emoji-1F1F8-1F1EA { background-position: -300px -160px; }
+.emoji-1F1F8-1F1EC { background-position: -300px -180px; }
+.emoji-1F1F8-1F1ED { background-position: -300px -200px; }
+.emoji-1F1F8-1F1EE { background-position: -300px -220px; }
+.emoji-1F1F8-1F1EF { background-position: -300px -240px; }
+.emoji-1F1F8-1F1F0 { background-position: -300px -260px; }
+.emoji-1F1F8-1F1F1 { background-position: -300px -280px; }
+.emoji-1F1F8-1F1F2 { background-position: 0px -300px; }
+.emoji-1F1F8-1F1F3 { background-position: -20px -300px; }
+.emoji-1F1F8-1F1F4 { background-position: -40px -300px; }
+.emoji-1F1F8-1F1F7 { background-position: -60px -300px; }
+.emoji-1F1F8-1F1F8 { background-position: -80px -300px; }
+.emoji-1F1F8-1F1F9 { background-position: -100px -300px; }
+.emoji-1F1F8-1F1FB { background-position: -120px -300px; }
+.emoji-1F1F8-1F1FD { background-position: -140px -300px; }
+.emoji-1F1F8-1F1FE { background-position: -160px -300px; }
+.emoji-1F1F8-1F1FF { background-position: -180px -300px; }
+.emoji-1F1F9-1F1E6 { background-position: -200px -300px; }
+.emoji-1F1F9-1F1E8 { background-position: -220px -300px; }
+.emoji-1F1F9-1F1E9 { background-position: -240px -300px; }
+.emoji-1F1F9-1F1EB { background-position: -260px -300px; }
+.emoji-1F1F9-1F1EC { background-position: -280px -300px; }
+.emoji-1F1F9-1F1ED { background-position: -300px -300px; }
+.emoji-1F1F9-1F1EF { background-position: -320px 0px; }
+.emoji-1F1F9-1F1F0 { background-position: -320px -20px; }
+.emoji-1F1F9-1F1F1 { background-position: -320px -40px; }
+.emoji-1F1F9-1F1F2 { background-position: -320px -60px; }
+.emoji-1F1F9-1F1F3 { background-position: -320px -80px; }
+.emoji-1F1F9-1F1F4 { background-position: -320px -100px; }
+.emoji-1F1F9-1F1F7 { background-position: -320px -120px; }
+.emoji-1F1F9-1F1F9 { background-position: -320px -140px; }
+.emoji-1F1F9-1F1FB { background-position: -320px -160px; }
+.emoji-1F1F9-1F1FC { background-position: -320px -180px; }
+.emoji-1F1F9-1F1FF { background-position: -320px -200px; }
+.emoji-1F1FA-1F1E6 { background-position: -320px -220px; }
+.emoji-1F1FA-1F1EC { background-position: -320px -240px; }
+.emoji-1F1FA-1F1F2 { background-position: -320px -260px; }
+.emoji-1F1FA-1F1F8 { background-position: -320px -280px; }
+.emoji-1F1FA-1F1FE { background-position: -320px -300px; }
+.emoji-1F1FA-1F1FF { background-position: 0px -320px; }
+.emoji-1F1FB-1F1E6 { background-position: -20px -320px; }
+.emoji-1F1FB-1F1E8 { background-position: -40px -320px; }
+.emoji-1F1FB-1F1EA { background-position: -60px -320px; }
+.emoji-1F1FB-1F1EC { background-position: -80px -320px; }
+.emoji-1F1FB-1F1EE { background-position: -100px -320px; }
+.emoji-1F1FB-1F1F3 { background-position: -120px -320px; }
+.emoji-1F1FB-1F1FA { background-position: -140px -320px; }
+.emoji-1F1FC-1F1EB { background-position: -160px -320px; }
+.emoji-1F1FC-1F1F8 { background-position: -180px -320px; }
+.emoji-1F1FD-1F1F0 { background-position: -200px -320px; }
+.emoji-1F1FE-1F1EA { background-position: -220px -320px; }
+.emoji-1F1FE-1F1F9 { background-position: -240px -320px; }
+.emoji-1F1FF-1F1E6 { background-position: -260px -320px; }
+.emoji-1F1FF-1F1F2 { background-position: -280px -320px; }
+.emoji-1F1FF-1F1FC { background-position: -300px -320px; }
+.emoji-1F201 { background-position: -320px -320px; }
+.emoji-1F202 { background-position: -340px 0px; }
+.emoji-1F21A { background-position: -340px -20px; }
+.emoji-1F22F { background-position: -340px -40px; }
+.emoji-1F232 { background-position: -340px -60px; }
+.emoji-1F233 { background-position: -340px -80px; }
+.emoji-1F234 { background-position: -340px -100px; }
+.emoji-1F235 { background-position: -340px -120px; }
+.emoji-1F236 { background-position: -340px -140px; }
+.emoji-1F237 { background-position: -340px -160px; }
+.emoji-1F238 { background-position: -340px -180px; }
+.emoji-1F239 { background-position: -340px -200px; }
+.emoji-1F23A { background-position: -340px -220px; }
+.emoji-1F250 { background-position: -340px -240px; }
+.emoji-1F251 { background-position: -340px -260px; }
+.emoji-1F300 { background-position: -340px -280px; }
+.emoji-1F301 { background-position: -340px -300px; }
+.emoji-1F302 { background-position: -340px -320px; }
+.emoji-1F303 { background-position: 0px -340px; }
+.emoji-1F304 { background-position: -20px -340px; }
+.emoji-1F305 { background-position: -40px -340px; }
+.emoji-1F306 { background-position: -60px -340px; }
+.emoji-1F307 { background-position: -80px -340px; }
+.emoji-1F308 { background-position: -100px -340px; }
+.emoji-1F309 { background-position: -120px -340px; }
+.emoji-1F30A { background-position: -140px -340px; }
+.emoji-1F30B { background-position: -160px -340px; }
+.emoji-1F30C { background-position: -180px -340px; }
+.emoji-1F30D { background-position: -200px -340px; }
+.emoji-1F30E { background-position: -220px -340px; }
+.emoji-1F30F { background-position: -240px -340px; }
+.emoji-1F310 { background-position: -260px -340px; }
+.emoji-1F311 { background-position: -280px -340px; }
+.emoji-1F312 { background-position: -300px -340px; }
+.emoji-1F313 { background-position: -320px -340px; }
+.emoji-1F314 { background-position: -340px -340px; }
+.emoji-1F315 { background-position: -360px 0px; }
+.emoji-1F316 { background-position: -360px -20px; }
+.emoji-1F317 { background-position: -360px -40px; }
+.emoji-1F318 { background-position: -360px -60px; }
+.emoji-1F319 { background-position: -360px -80px; }
+.emoji-1F31A { background-position: -360px -100px; }
+.emoji-1F31B { background-position: -360px -120px; }
+.emoji-1F31C { background-position: -360px -140px; }
+.emoji-1F31D { background-position: -360px -160px; }
+.emoji-1F31E { background-position: -360px -180px; }
+.emoji-1F31F { background-position: -360px -200px; }
+.emoji-1F320 { background-position: -360px -220px; }
+.emoji-1F321 { background-position: -360px -240px; }
+.emoji-1F324 { background-position: -360px -260px; }
+.emoji-1F325 { background-position: -360px -280px; }
+.emoji-1F326 { background-position: -360px -300px; }
+.emoji-1F327 { background-position: -360px -320px; }
+.emoji-1F328 { background-position: -360px -340px; }
+.emoji-1F329 { background-position: 0px -360px; }
+.emoji-1F32A { background-position: -20px -360px; }
+.emoji-1F32B { background-position: -40px -360px; }
+.emoji-1F32C { background-position: -60px -360px; }
+.emoji-1F32D { background-position: -80px -360px; }
+.emoji-1F32E { background-position: -100px -360px; }
+.emoji-1F32F { background-position: -120px -360px; }
+.emoji-1F330 { background-position: -140px -360px; }
+.emoji-1F331 { background-position: -160px -360px; }
+.emoji-1F332 { background-position: -180px -360px; }
+.emoji-1F333 { background-position: -200px -360px; }
+.emoji-1F334 { background-position: -220px -360px; }
+.emoji-1F335 { background-position: -240px -360px; }
+.emoji-1F336 { background-position: -260px -360px; }
+.emoji-1F337 { background-position: -280px -360px; }
+.emoji-1F338 { background-position: -300px -360px; }
+.emoji-1F339 { background-position: -320px -360px; }
+.emoji-1F33A { background-position: -340px -360px; }
+.emoji-1F33B { background-position: -360px -360px; }
+.emoji-1F33C { background-position: -380px 0px; }
+.emoji-1F33D { background-position: -380px -20px; }
+.emoji-1F33E { background-position: -380px -40px; }
+.emoji-1F33F { background-position: -380px -60px; }
+.emoji-1F340 { background-position: -380px -80px; }
+.emoji-1F341 { background-position: -380px -100px; }
+.emoji-1F342 { background-position: -380px -120px; }
+.emoji-1F343 { background-position: -380px -140px; }
+.emoji-1F344 { background-position: -380px -160px; }
+.emoji-1F345 { background-position: -380px -180px; }
+.emoji-1F346 { background-position: -380px -200px; }
+.emoji-1F347 { background-position: -380px -220px; }
+.emoji-1F348 { background-position: -380px -240px; }
+.emoji-1F349 { background-position: -380px -260px; }
+.emoji-1F34A { background-position: -380px -280px; }
+.emoji-1F34B { background-position: -380px -300px; }
+.emoji-1F34C { background-position: -380px -320px; }
+.emoji-1F34D { background-position: -380px -340px; }
+.emoji-1F34E { background-position: -380px -360px; }
+.emoji-1F34F { background-position: 0px -380px; }
+.emoji-1F350 { background-position: -20px -380px; }
+.emoji-1F351 { background-position: -40px -380px; }
+.emoji-1F352 { background-position: -60px -380px; }
+.emoji-1F353 { background-position: -80px -380px; }
+.emoji-1F354 { background-position: -100px -380px; }
+.emoji-1F355 { background-position: -120px -380px; }
+.emoji-1F356 { background-position: -140px -380px; }
+.emoji-1F357 { background-position: -160px -380px; }
+.emoji-1F358 { background-position: -180px -380px; }
+.emoji-1F359 { background-position: -200px -380px; }
+.emoji-1F35A { background-position: -220px -380px; }
+.emoji-1F35B { background-position: -240px -380px; }
+.emoji-1F35C { background-position: -260px -380px; }
+.emoji-1F35D { background-position: -280px -380px; }
+.emoji-1F35E { background-position: -300px -380px; }
+.emoji-1F35F { background-position: -320px -380px; }
+.emoji-1F360 { background-position: -340px -380px; }
+.emoji-1F361 { background-position: -360px -380px; }
+.emoji-1F362 { background-position: -380px -380px; }
+.emoji-1F363 { background-position: -400px 0px; }
+.emoji-1F364 { background-position: -400px -20px; }
+.emoji-1F365 { background-position: -400px -40px; }
+.emoji-1F366 { background-position: -400px -60px; }
+.emoji-1F367 { background-position: -400px -80px; }
+.emoji-1F368 { background-position: -400px -100px; }
+.emoji-1F369 { background-position: -400px -120px; }
+.emoji-1F36A { background-position: -400px -140px; }
+.emoji-1F36B { background-position: -400px -160px; }
+.emoji-1F36C { background-position: -400px -180px; }
+.emoji-1F36D { background-position: -400px -200px; }
+.emoji-1F36E { background-position: -400px -220px; }
+.emoji-1F36F { background-position: -400px -240px; }
+.emoji-1F370 { background-position: -400px -260px; }
+.emoji-1F371 { background-position: -400px -280px; }
+.emoji-1F372 { background-position: -400px -300px; }
+.emoji-1F373 { background-position: -400px -320px; }
+.emoji-1F374 { background-position: -400px -340px; }
+.emoji-1F375 { background-position: -400px -360px; }
+.emoji-1F376 { background-position: -400px -380px; }
+.emoji-1F377 { background-position: 0px -400px; }
+.emoji-1F378 { background-position: -20px -400px; }
+.emoji-1F379 { background-position: -40px -400px; }
+.emoji-1F37A { background-position: -60px -400px; }
+.emoji-1F37B { background-position: -80px -400px; }
+.emoji-1F37C { background-position: -100px -400px; }
+.emoji-1F37D { background-position: -120px -400px; }
+.emoji-1F37E { background-position: -140px -400px; }
+.emoji-1F37F { background-position: -160px -400px; }
+.emoji-1F380 { background-position: -180px -400px; }
+.emoji-1F381 { background-position: -200px -400px; }
+.emoji-1F382 { background-position: -220px -400px; }
+.emoji-1F383 { background-position: -240px -400px; }
+.emoji-1F384 { background-position: -260px -400px; }
+.emoji-1F385 { background-position: -280px -400px; }
+.emoji-1F385-1F3FB { background-position: -300px -400px; }
+.emoji-1F385-1F3FC { background-position: -320px -400px; }
+.emoji-1F385-1F3FD { background-position: -340px -400px; }
+.emoji-1F385-1F3FE { background-position: -360px -400px; }
+.emoji-1F385-1F3FF { background-position: -380px -400px; }
+.emoji-1F386 { background-position: -400px -400px; }
+.emoji-1F387 { background-position: -420px 0px; }
+.emoji-1F388 { background-position: -420px -20px; }
+.emoji-1F389 { background-position: -420px -40px; }
+.emoji-1F38A { background-position: -420px -60px; }
+.emoji-1F38B { background-position: -420px -80px; }
+.emoji-1F38C { background-position: -420px -100px; }
+.emoji-1F38D { background-position: -420px -120px; }
+.emoji-1F38E { background-position: -420px -140px; }
+.emoji-1F38F { background-position: -420px -160px; }
+.emoji-1F390 { background-position: -420px -180px; }
+.emoji-1F391 { background-position: -420px -200px; }
+.emoji-1F392 { background-position: -420px -220px; }
+.emoji-1F393 { background-position: -420px -240px; }
+.emoji-1F394 { background-position: -420px -260px; }
+.emoji-1F395 { background-position: -420px -280px; }
+.emoji-1F396 { background-position: -420px -300px; }
+.emoji-1F397 { background-position: -420px -320px; }
+.emoji-1F398 { background-position: -420px -340px; }
+.emoji-1F399 { background-position: -420px -360px; }
+.emoji-1F39A { background-position: -420px -380px; }
+.emoji-1F39B { background-position: -420px -400px; }
+.emoji-1F39C { background-position: 0px -420px; }
+.emoji-1F39D { background-position: -20px -420px; }
+.emoji-1F39E { background-position: -40px -420px; }
+.emoji-1F39F { background-position: -60px -420px; }
+.emoji-1F3A0 { background-position: -80px -420px; }
+.emoji-1F3A1 { background-position: -100px -420px; }
+.emoji-1F3A2 { background-position: -120px -420px; }
+.emoji-1F3A3 { background-position: -140px -420px; }
+.emoji-1F3A4 { background-position: -160px -420px; }
+.emoji-1F3A5 { background-position: -180px -420px; }
+.emoji-1F3A6 { background-position: -200px -420px; }
+.emoji-1F3A7 { background-position: -220px -420px; }
+.emoji-1F3A8 { background-position: -240px -420px; }
+.emoji-1F3A9 { background-position: -260px -420px; }
+.emoji-1F3AA { background-position: -280px -420px; }
+.emoji-1F3AB { background-position: -300px -420px; }
+.emoji-1F3AC { background-position: -320px -420px; }
+.emoji-1F3AD { background-position: -340px -420px; }
+.emoji-1F3AE { background-position: -360px -420px; }
+.emoji-1F3AF { background-position: -380px -420px; }
+.emoji-1F3B0 { background-position: -400px -420px; }
+.emoji-1F3B1 { background-position: -420px -420px; }
+.emoji-1F3B2 { background-position: -440px 0px; }
+.emoji-1F3B3 { background-position: -440px -20px; }
+.emoji-1F3B4 { background-position: -440px -40px; }
+.emoji-1F3B5 { background-position: -440px -60px; }
+.emoji-1F3B6 { background-position: -440px -80px; }
+.emoji-1F3B7 { background-position: -440px -100px; }
+.emoji-1F3B8 { background-position: -440px -120px; }
+.emoji-1F3B9 { background-position: -440px -140px; }
+.emoji-1F3BA { background-position: -440px -160px; }
+.emoji-1F3BB { background-position: -440px -180px; }
+.emoji-1F3BC { background-position: -440px -200px; }
+.emoji-1F3BD { background-position: -440px -220px; }
+.emoji-1F3BE { background-position: -440px -240px; }
+.emoji-1F3BF { background-position: -440px -260px; }
+.emoji-1F3C0 { background-position: -440px -280px; }
+.emoji-1F3C1 { background-position: -440px -300px; }
+.emoji-1F3C2 { background-position: -440px -320px; }
+.emoji-1F3C3 { background-position: -440px -340px; }
+.emoji-1F3C3-1F3FB { background-position: -440px -360px; }
+.emoji-1F3C3-1F3FC { background-position: -440px -380px; }
+.emoji-1F3C3-1F3FD { background-position: -440px -400px; }
+.emoji-1F3C3-1F3FE { background-position: -440px -420px; }
+.emoji-1F3C3-1F3FF { background-position: 0px -440px; }
+.emoji-1F3C4 { background-position: -20px -440px; }
+.emoji-1F3C4-1F3FB { background-position: -40px -440px; }
+.emoji-1F3C4-1F3FC { background-position: -60px -440px; }
+.emoji-1F3C4-1F3FD { background-position: -80px -440px; }
+.emoji-1F3C4-1F3FE { background-position: -100px -440px; }
+.emoji-1F3C4-1F3FF { background-position: -120px -440px; }
+.emoji-1F3C5 { background-position: -140px -440px; }
+.emoji-1F3C6 { background-position: -160px -440px; }
+.emoji-1F3C7 { background-position: -180px -440px; }
+.emoji-1F3C7-1F3FB { background-position: -200px -440px; }
+.emoji-1F3C7-1F3FC { background-position: -220px -440px; }
+.emoji-1F3C7-1F3FD { background-position: -240px -440px; }
+.emoji-1F3C7-1F3FE { background-position: -260px -440px; }
+.emoji-1F3C7-1F3FF { background-position: -280px -440px; }
+.emoji-1F3C8 { background-position: -300px -440px; }
+.emoji-1F3C9 { background-position: -320px -440px; }
+.emoji-1F3CA { background-position: -340px -440px; }
+.emoji-1F3CA-1F3FB { background-position: -360px -440px; }
+.emoji-1F3CA-1F3FC { background-position: -380px -440px; }
+.emoji-1F3CA-1F3FD { background-position: -400px -440px; }
+.emoji-1F3CA-1F3FE { background-position: -420px -440px; }
+.emoji-1F3CA-1F3FF { background-position: -440px -440px; }
+.emoji-1F3CB { background-position: -460px 0px; }
+.emoji-1F3CB-1F3FB { background-position: -460px -20px; }
+.emoji-1F3CB-1F3FC { background-position: -460px -40px; }
+.emoji-1F3CB-1F3FD { background-position: -460px -60px; }
+.emoji-1F3CB-1F3FE { background-position: -460px -80px; }
+.emoji-1F3CB-1F3FF { background-position: -460px -100px; }
+.emoji-1F3CC { background-position: -460px -120px; }
+.emoji-1F3CD { background-position: -460px -140px; }
+.emoji-1F3CE { background-position: -460px -160px; }
+.emoji-1F3CF { background-position: -460px -180px; }
+.emoji-1F3D0 { background-position: -460px -200px; }
+.emoji-1F3D1 { background-position: -460px -220px; }
+.emoji-1F3D2 { background-position: -460px -240px; }
+.emoji-1F3D3 { background-position: -460px -260px; }
+.emoji-1F3D4 { background-position: -460px -280px; }
+.emoji-1F3D5 { background-position: -460px -300px; }
+.emoji-1F3D6 { background-position: -460px -320px; }
+.emoji-1F3D7 { background-position: -460px -340px; }
+.emoji-1F3D8 { background-position: -460px -360px; }
+.emoji-1F3D9 { background-position: -460px -380px; }
+.emoji-1F3DA { background-position: -460px -400px; }
+.emoji-1F3DB { background-position: -460px -420px; }
+.emoji-1F3DC { background-position: -460px -440px; }
+.emoji-1F3DD { background-position: 0px -460px; }
+.emoji-1F3DE { background-position: -20px -460px; }
+.emoji-1F3DF { background-position: -40px -460px; }
+.emoji-1F3E0 { background-position: -60px -460px; }
+.emoji-1F3E1 { background-position: -80px -460px; }
+.emoji-1F3E2 { background-position: -100px -460px; }
+.emoji-1F3E3 { background-position: -120px -460px; }
+.emoji-1F3E4 { background-position: -140px -460px; }
+.emoji-1F3E5 { background-position: -160px -460px; }
+.emoji-1F3E6 { background-position: -180px -460px; }
+.emoji-1F3E7 { background-position: -200px -460px; }
+.emoji-1F3E8 { background-position: -220px -460px; }
+.emoji-1F3E9 { background-position: -240px -460px; }
+.emoji-1F3EA { background-position: -260px -460px; }
+.emoji-1F3EB { background-position: -280px -460px; }
+.emoji-1F3EC { background-position: -300px -460px; }
+.emoji-1F3ED { background-position: -320px -460px; }
+.emoji-1F3EE { background-position: -340px -460px; }
+.emoji-1F3EF { background-position: -360px -460px; }
+.emoji-1F3F0 { background-position: -380px -460px; }
+.emoji-1F3F1 { background-position: -400px -460px; }
+.emoji-1F3F2 { background-position: -420px -460px; }
+.emoji-1F3F3 { background-position: -440px -460px; }
+.emoji-1F3F4 { background-position: -460px -460px; }
+.emoji-1F3F5 { background-position: -480px 0px; }
+.emoji-1F3F6 { background-position: -480px -20px; }
+.emoji-1F3F7 { background-position: -480px -40px; }
+.emoji-1F3F8 { background-position: -480px -60px; }
+.emoji-1F3F9 { background-position: -480px -80px; }
+.emoji-1F3FA { background-position: -480px -100px; }
+.emoji-1F3FB { background-position: -480px -120px; }
+.emoji-1F3FC { background-position: -480px -140px; }
+.emoji-1F3FD { background-position: -480px -160px; }
+.emoji-1F3FE { background-position: -480px -180px; }
+.emoji-1F3FF { background-position: -480px -200px; }
+.emoji-1F400 { background-position: -480px -220px; }
+.emoji-1F401 { background-position: -480px -240px; }
+.emoji-1F402 { background-position: -480px -260px; }
+.emoji-1F403 { background-position: -480px -280px; }
+.emoji-1F404 { background-position: -480px -300px; }
+.emoji-1F405 { background-position: -480px -320px; }
+.emoji-1F406 { background-position: -480px -340px; }
+.emoji-1F407 { background-position: -480px -360px; }
+.emoji-1F408 { background-position: -480px -380px; }
+.emoji-1F409 { background-position: -480px -400px; }
+.emoji-1F40A { background-position: -480px -420px; }
+.emoji-1F40B { background-position: -480px -440px; }
+.emoji-1F40C { background-position: -480px -460px; }
+.emoji-1F40D { background-position: 0px -480px; }
+.emoji-1F40E { background-position: -20px -480px; }
+.emoji-1F40F { background-position: -40px -480px; }
+.emoji-1F410 { background-position: -60px -480px; }
+.emoji-1F411 { background-position: -80px -480px; }
+.emoji-1F412 { background-position: -100px -480px; }
+.emoji-1F413 { background-position: -120px -480px; }
+.emoji-1F414 { background-position: -140px -480px; }
+.emoji-1F415 { background-position: -160px -480px; }
+.emoji-1F416 { background-position: -180px -480px; }
+.emoji-1F417 { background-position: -200px -480px; }
+.emoji-1F418 { background-position: -220px -480px; }
+.emoji-1F419 { background-position: -240px -480px; }
+.emoji-1F41A { background-position: -260px -480px; }
+.emoji-1F41B { background-position: -280px -480px; }
+.emoji-1F41C { background-position: -300px -480px; }
+.emoji-1F41D { background-position: -320px -480px; }
+.emoji-1F41E { background-position: -340px -480px; }
+.emoji-1F41F { background-position: -360px -480px; }
+.emoji-1F420 { background-position: -380px -480px; }
+.emoji-1F421 { background-position: -400px -480px; }
+.emoji-1F422 { background-position: -420px -480px; }
+.emoji-1F423 { background-position: -440px -480px; }
+.emoji-1F424 { background-position: -460px -480px; }
+.emoji-1F425 { background-position: -480px -480px; }
+.emoji-1F426 { background-position: -500px 0px; }
+.emoji-1F427 { background-position: -500px -20px; }
+.emoji-1F428 { background-position: -500px -40px; }
+.emoji-1F429 { background-position: -500px -60px; }
+.emoji-1F42A { background-position: -500px -80px; }
+.emoji-1F42B { background-position: -500px -100px; }
+.emoji-1F42C { background-position: -500px -120px; }
+.emoji-1F42D { background-position: -500px -140px; }
+.emoji-1F42E { background-position: -500px -160px; }
+.emoji-1F42F { background-position: -500px -180px; }
+.emoji-1F430 { background-position: -500px -200px; }
+.emoji-1F431 { background-position: -500px -220px; }
+.emoji-1F432 { background-position: -500px -240px; }
+.emoji-1F433 { background-position: -500px -260px; }
+.emoji-1F434 { background-position: -500px -280px; }
+.emoji-1F435 { background-position: -500px -300px; }
+.emoji-1F436 { background-position: -500px -320px; }
+.emoji-1F437 { background-position: -500px -340px; }
+.emoji-1F438 { background-position: -500px -360px; }
+.emoji-1F439 { background-position: -500px -380px; }
+.emoji-1F43A { background-position: -500px -400px; }
+.emoji-1F43B { background-position: -500px -420px; }
+.emoji-1F43C { background-position: -500px -440px; }
+.emoji-1F43D { background-position: -500px -460px; }
+.emoji-1F43E { background-position: -500px -480px; }
+.emoji-1F43F { background-position: 0px -500px; }
+.emoji-1F440 { background-position: -20px -500px; }
+.emoji-1F441 { background-position: -40px -500px; }
+.emoji-1F441-1F5E8 { background-position: -60px -500px; }
+.emoji-1F442 { background-position: -80px -500px; }
+.emoji-1F442-1F3FB { background-position: -100px -500px; }
+.emoji-1F442-1F3FC { background-position: -120px -500px; }
+.emoji-1F442-1F3FD { background-position: -140px -500px; }
+.emoji-1F442-1F3FE { background-position: -160px -500px; }
+.emoji-1F442-1F3FF { background-position: -180px -500px; }
+.emoji-1F443 { background-position: -200px -500px; }
+.emoji-1F443-1F3FB { background-position: -220px -500px; }
+.emoji-1F443-1F3FC { background-position: -240px -500px; }
+.emoji-1F443-1F3FD { background-position: -260px -500px; }
+.emoji-1F443-1F3FE { background-position: -280px -500px; }
+.emoji-1F443-1F3FF { background-position: -300px -500px; }
+.emoji-1F444 { background-position: -320px -500px; }
+.emoji-1F445 { background-position: -340px -500px; }
+.emoji-1F446 { background-position: -360px -500px; }
+.emoji-1F446-1F3FB { background-position: -380px -500px; }
+.emoji-1F446-1F3FC { background-position: -400px -500px; }
+.emoji-1F446-1F3FD { background-position: -420px -500px; }
+.emoji-1F446-1F3FE { background-position: -440px -500px; }
+.emoji-1F446-1F3FF { background-position: -460px -500px; }
+.emoji-1F447 { background-position: -480px -500px; }
+.emoji-1F447-1F3FB { background-position: -500px -500px; }
+.emoji-1F447-1F3FC { background-position: -520px 0px; }
+.emoji-1F447-1F3FD { background-position: -520px -20px; }
+.emoji-1F447-1F3FE { background-position: -520px -40px; }
+.emoji-1F447-1F3FF { background-position: -520px -60px; }
+.emoji-1F448 { background-position: -520px -80px; }
+.emoji-1F448-1F3FB { background-position: -520px -100px; }
+.emoji-1F448-1F3FC { background-position: -520px -120px; }
+.emoji-1F448-1F3FD { background-position: -520px -140px; }
+.emoji-1F448-1F3FE { background-position: -520px -160px; }
+.emoji-1F448-1F3FF { background-position: -520px -180px; }
+.emoji-1F449 { background-position: -520px -200px; }
+.emoji-1F449-1F3FB { background-position: -520px -220px; }
+.emoji-1F449-1F3FC { background-position: -520px -240px; }
+.emoji-1F449-1F3FD { background-position: -520px -260px; }
+.emoji-1F449-1F3FE { background-position: -520px -280px; }
+.emoji-1F449-1F3FF { background-position: -520px -300px; }
+.emoji-1F44A { background-position: -520px -320px; }
+.emoji-1F44A-1F3FB { background-position: -520px -340px; }
+.emoji-1F44A-1F3FC { background-position: -520px -360px; }
+.emoji-1F44A-1F3FD { background-position: -520px -380px; }
+.emoji-1F44A-1F3FE { background-position: -520px -400px; }
+.emoji-1F44A-1F3FF { background-position: -520px -420px; }
+.emoji-1F44B { background-position: -520px -440px; }
+.emoji-1F44B-1F3FB { background-position: -520px -460px; }
+.emoji-1F44B-1F3FC { background-position: -520px -480px; }
+.emoji-1F44B-1F3FD { background-position: -520px -500px; }
+.emoji-1F44B-1F3FE { background-position: 0px -520px; }
+.emoji-1F44B-1F3FF { background-position: -20px -520px; }
+.emoji-1F44C { background-position: -40px -520px; }
+.emoji-1F44C-1F3FB { background-position: -60px -520px; }
+.emoji-1F44C-1F3FC { background-position: -80px -520px; }
+.emoji-1F44C-1F3FD { background-position: -100px -520px; }
+.emoji-1F44C-1F3FE { background-position: -120px -520px; }
+.emoji-1F44C-1F3FF { background-position: -140px -520px; }
+.emoji-1F44D { background-position: -160px -520px; }
+.emoji-1F44D-1F3FB { background-position: -180px -520px; }
+.emoji-1F44D-1F3FC { background-position: -200px -520px; }
+.emoji-1F44D-1F3FD { background-position: -220px -520px; }
+.emoji-1F44D-1F3FE { background-position: -240px -520px; }
+.emoji-1F44D-1F3FF { background-position: -260px -520px; }
+.emoji-1F44E { background-position: -280px -520px; }
+.emoji-1F44E-1F3FB { background-position: -300px -520px; }
+.emoji-1F44E-1F3FC { background-position: -320px -520px; }
+.emoji-1F44E-1F3FD { background-position: -340px -520px; }
+.emoji-1F44E-1F3FE { background-position: -360px -520px; }
+.emoji-1F44E-1F3FF { background-position: -380px -520px; }
+.emoji-1F44F { background-position: -400px -520px; }
+.emoji-1F44F-1F3FB { background-position: -420px -520px; }
+.emoji-1F44F-1F3FC { background-position: -440px -520px; }
+.emoji-1F44F-1F3FD { background-position: -460px -520px; }
+.emoji-1F44F-1F3FE { background-position: -480px -520px; }
+.emoji-1F44F-1F3FF { background-position: -500px -520px; }
+.emoji-1F450 { background-position: -520px -520px; }
+.emoji-1F450-1F3FB { background-position: -540px 0px; }
+.emoji-1F450-1F3FC { background-position: -540px -20px; }
+.emoji-1F450-1F3FD { background-position: -540px -40px; }
+.emoji-1F450-1F3FE { background-position: -540px -60px; }
+.emoji-1F450-1F3FF { background-position: -540px -80px; }
+.emoji-1F451 { background-position: -540px -100px; }
+.emoji-1F452 { background-position: -540px -120px; }
+.emoji-1F453 { background-position: -540px -140px; }
+.emoji-1F454 { background-position: -540px -160px; }
+.emoji-1F455 { background-position: -540px -180px; }
+.emoji-1F456 { background-position: -540px -200px; }
+.emoji-1F457 { background-position: -540px -220px; }
+.emoji-1F458 { background-position: -540px -240px; }
+.emoji-1F459 { background-position: -540px -260px; }
+.emoji-1F45A { background-position: -540px -280px; }
+.emoji-1F45B { background-position: -540px -300px; }
+.emoji-1F45C { background-position: -540px -320px; }
+.emoji-1F45D { background-position: -540px -340px; }
+.emoji-1F45E { background-position: -540px -360px; }
+.emoji-1F45F { background-position: -540px -380px; }
+.emoji-1F460 { background-position: -540px -400px; }
+.emoji-1F461 { background-position: -540px -420px; }
+.emoji-1F462 { background-position: -540px -440px; }
+.emoji-1F463 { background-position: -540px -460px; }
+.emoji-1F464 { background-position: -540px -480px; }
+.emoji-1F465 { background-position: -540px -500px; }
+.emoji-1F466 { background-position: -540px -520px; }
+.emoji-1F466-1F3FB { background-position: 0px -540px; }
+.emoji-1F466-1F3FC { background-position: -20px -540px; }
+.emoji-1F466-1F3FD { background-position: -40px -540px; }
+.emoji-1F466-1F3FE { background-position: -60px -540px; }
+.emoji-1F466-1F3FF { background-position: -80px -540px; }
+.emoji-1F467 { background-position: -100px -540px; }
+.emoji-1F467-1F3FB { background-position: -120px -540px; }
+.emoji-1F467-1F3FC { background-position: -140px -540px; }
+.emoji-1F467-1F3FD { background-position: -160px -540px; }
+.emoji-1F467-1F3FE { background-position: -180px -540px; }
+.emoji-1F467-1F3FF { background-position: -200px -540px; }
+.emoji-1F468 { background-position: -220px -540px; }
+.emoji-1F468-1F3FB { background-position: -240px -540px; }
+.emoji-1F468-1F3FC { background-position: -260px -540px; }
+.emoji-1F468-1F3FD { background-position: -280px -540px; }
+.emoji-1F468-1F3FE { background-position: -300px -540px; }
+.emoji-1F468-1F3FF { background-position: -320px -540px; }
+.emoji-1F468-1F468-1F466 { background-position: -340px -540px; }
+.emoji-1F468-1F468-1F466-1F466 { background-position: -360px -540px; }
+.emoji-1F468-1F468-1F467 { background-position: -380px -540px; }
+.emoji-1F468-1F468-1F467-1F466 { background-position: -400px -540px; }
+.emoji-1F468-1F468-1F467-1F467 { background-position: -420px -540px; }
+.emoji-1F468-1F469-1F466-1F466 { background-position: -440px -540px; }
+.emoji-1F468-1F469-1F467 { background-position: -460px -540px; }
+.emoji-1F468-1F469-1F467-1F466 { background-position: -480px -540px; }
+.emoji-1F468-1F469-1F467-1F467 { background-position: -500px -540px; }
+.emoji-1F468-2764-1F468 { background-position: -520px -540px; }
+.emoji-1F468-2764-1F48B-1F468 { background-position: -540px -540px; }
+.emoji-1F469 { background-position: -560px 0px; }
+.emoji-1F469-1F3FB { background-position: -560px -20px; }
+.emoji-1F469-1F3FC { background-position: -560px -40px; }
+.emoji-1F469-1F3FD { background-position: -560px -60px; }
+.emoji-1F469-1F3FE { background-position: -560px -80px; }
+.emoji-1F469-1F3FF { background-position: -560px -100px; }
+.emoji-1F469-1F469-1F466 { background-position: -560px -120px; }
+.emoji-1F469-1F469-1F466-1F466 { background-position: -560px -140px; }
+.emoji-1F469-1F469-1F467 { background-position: -560px -160px; }
+.emoji-1F469-1F469-1F467-1F466 { background-position: -560px -180px; }
+.emoji-1F469-1F469-1F467-1F467 { background-position: -560px -200px; }
+.emoji-1F469-2764-1F469 { background-position: -560px -220px; }
+.emoji-1F469-2764-1F48B-1F469 { background-position: -560px -240px; }
+.emoji-1F46A { background-position: -560px -260px; }
+.emoji-1F46B { background-position: -560px -280px; }
+.emoji-1F46C { background-position: -560px -300px; }
+.emoji-1F46D { background-position: -560px -320px; }
+.emoji-1F46E { background-position: -560px -340px; }
+.emoji-1F46E-1F3FB { background-position: -560px -360px; }
+.emoji-1F46E-1F3FC { background-position: -560px -380px; }
+.emoji-1F46E-1F3FD { background-position: -560px -400px; }
+.emoji-1F46E-1F3FE { background-position: -560px -420px; }
+.emoji-1F46E-1F3FF { background-position: -560px -440px; }
+.emoji-1F46F { background-position: -560px -460px; }
+.emoji-1F470 { background-position: -560px -480px; }
+.emoji-1F470-1F3FB { background-position: -560px -500px; }
+.emoji-1F470-1F3FC { background-position: -560px -520px; }
+.emoji-1F470-1F3FD { background-position: -560px -540px; }
+.emoji-1F470-1F3FE { background-position: 0px -560px; }
+.emoji-1F470-1F3FF { background-position: -20px -560px; }
+.emoji-1F471 { background-position: -40px -560px; }
+.emoji-1F471-1F3FB { background-position: -60px -560px; }
+.emoji-1F471-1F3FC { background-position: -80px -560px; }
+.emoji-1F471-1F3FD { background-position: -100px -560px; }
+.emoji-1F471-1F3FE { background-position: -120px -560px; }
+.emoji-1F471-1F3FF { background-position: -140px -560px; }
+.emoji-1F472 { background-position: -160px -560px; }
+.emoji-1F472-1F3FB { background-position: -180px -560px; }
+.emoji-1F472-1F3FC { background-position: -200px -560px; }
+.emoji-1F472-1F3FD { background-position: -220px -560px; }
+.emoji-1F472-1F3FE { background-position: -240px -560px; }
+.emoji-1F472-1F3FF { background-position: -260px -560px; }
+.emoji-1F473 { background-position: -280px -560px; }
+.emoji-1F473-1F3FB { background-position: -300px -560px; }
+.emoji-1F473-1F3FC { background-position: -320px -560px; }
+.emoji-1F473-1F3FD { background-position: -340px -560px; }
+.emoji-1F473-1F3FE { background-position: -360px -560px; }
+.emoji-1F473-1F3FF { background-position: -380px -560px; }
+.emoji-1F474 { background-position: -400px -560px; }
+.emoji-1F474-1F3FB { background-position: -420px -560px; }
+.emoji-1F474-1F3FC { background-position: -440px -560px; }
+.emoji-1F474-1F3FD { background-position: -460px -560px; }
+.emoji-1F474-1F3FE { background-position: -480px -560px; }
+.emoji-1F474-1F3FF { background-position: -500px -560px; }
+.emoji-1F475 { background-position: -520px -560px; }
+.emoji-1F475-1F3FB { background-position: -540px -560px; }
+.emoji-1F475-1F3FC { background-position: -560px -560px; }
+.emoji-1F475-1F3FD { background-position: -580px 0px; }
+.emoji-1F475-1F3FE { background-position: -580px -20px; }
+.emoji-1F475-1F3FF { background-position: -580px -40px; }
+.emoji-1F476 { background-position: -580px -60px; }
+.emoji-1F476-1F3FB { background-position: -580px -80px; }
+.emoji-1F476-1F3FC { background-position: -580px -100px; }
+.emoji-1F476-1F3FD { background-position: -580px -120px; }
+.emoji-1F476-1F3FE { background-position: -580px -140px; }
+.emoji-1F476-1F3FF { background-position: -580px -160px; }
+.emoji-1F477 { background-position: -580px -180px; }
+.emoji-1F477-1F3FB { background-position: -580px -200px; }
+.emoji-1F477-1F3FC { background-position: -580px -220px; }
+.emoji-1F477-1F3FD { background-position: -580px -240px; }
+.emoji-1F477-1F3FE { background-position: -580px -260px; }
+.emoji-1F477-1F3FF { background-position: -580px -280px; }
+.emoji-1F478 { background-position: -580px -300px; }
+.emoji-1F478-1F3FB { background-position: -580px -320px; }
+.emoji-1F478-1F3FC { background-position: -580px -340px; }
+.emoji-1F478-1F3FD { background-position: -580px -360px; }
+.emoji-1F478-1F3FE { background-position: -580px -380px; }
+.emoji-1F478-1F3FF { background-position: -580px -400px; }
+.emoji-1F479 { background-position: -580px -420px; }
+.emoji-1F47A { background-position: -580px -440px; }
+.emoji-1F47B { background-position: -580px -460px; }
+.emoji-1F47C { background-position: -580px -480px; }
+.emoji-1F47C-1F3FB { background-position: -580px -500px; }
+.emoji-1F47C-1F3FC { background-position: -580px -520px; }
+.emoji-1F47C-1F3FD { background-position: -580px -540px; }
+.emoji-1F47C-1F3FE { background-position: -580px -560px; }
+.emoji-1F47C-1F3FF { background-position: 0px -580px; }
+.emoji-1F47D { background-position: -20px -580px; }
+.emoji-1F47E { background-position: -40px -580px; }
+.emoji-1F47F { background-position: -60px -580px; }
+.emoji-1F480 { background-position: -80px -580px; }
+.emoji-1F481 { background-position: -100px -580px; }
+.emoji-1F481-1F3FB { background-position: -120px -580px; }
+.emoji-1F481-1F3FC { background-position: -140px -580px; }
+.emoji-1F481-1F3FD { background-position: -160px -580px; }
+.emoji-1F481-1F3FE { background-position: -180px -580px; }
+.emoji-1F481-1F3FF { background-position: -200px -580px; }
+.emoji-1F482 { background-position: -220px -580px; }
+.emoji-1F482-1F3FB { background-position: -240px -580px; }
+.emoji-1F482-1F3FC { background-position: -260px -580px; }
+.emoji-1F482-1F3FD { background-position: -280px -580px; }
+.emoji-1F482-1F3FE { background-position: -300px -580px; }
+.emoji-1F482-1F3FF { background-position: -320px -580px; }
+.emoji-1F483 { background-position: -340px -580px; }
+.emoji-1F483-1F3FB { background-position: -360px -580px; }
+.emoji-1F483-1F3FC { background-position: -380px -580px; }
+.emoji-1F483-1F3FD { background-position: -400px -580px; }
+.emoji-1F483-1F3FE { background-position: -420px -580px; }
+.emoji-1F483-1F3FF { background-position: -440px -580px; }
+.emoji-1F484 { background-position: -460px -580px; }
+.emoji-1F485 { background-position: -480px -580px; }
+.emoji-1F485-1F3FB { background-position: -500px -580px; }
+.emoji-1F485-1F3FC { background-position: -520px -580px; }
+.emoji-1F485-1F3FD { background-position: -540px -580px; }
+.emoji-1F485-1F3FE { background-position: -560px -580px; }
+.emoji-1F485-1F3FF { background-position: -580px -580px; }
+.emoji-1F486 { background-position: -600px 0px; }
+.emoji-1F486-1F3FB { background-position: -600px -20px; }
+.emoji-1F486-1F3FC { background-position: -600px -40px; }
+.emoji-1F486-1F3FD { background-position: -600px -60px; }
+.emoji-1F486-1F3FE { background-position: -600px -80px; }
+.emoji-1F486-1F3FF { background-position: -600px -100px; }
+.emoji-1F487 { background-position: -600px -120px; }
+.emoji-1F487-1F3FB { background-position: -600px -140px; }
+.emoji-1F487-1F3FC { background-position: -600px -160px; }
+.emoji-1F487-1F3FD { background-position: -600px -180px; }
+.emoji-1F487-1F3FE { background-position: -600px -200px; }
+.emoji-1F487-1F3FF { background-position: -600px -220px; }
+.emoji-1F488 { background-position: -600px -240px; }
+.emoji-1F489 { background-position: -600px -260px; }
+.emoji-1F48A { background-position: -600px -280px; }
+.emoji-1F48B { background-position: -600px -300px; }
+.emoji-1F48C { background-position: -600px -320px; }
+.emoji-1F48D { background-position: -600px -340px; }
+.emoji-1F48E { background-position: -600px -360px; }
+.emoji-1F48F { background-position: -600px -380px; }
+.emoji-1F490 { background-position: -600px -400px; }
+.emoji-1F491 { background-position: -600px -420px; }
+.emoji-1F492 { background-position: -600px -440px; }
+.emoji-1F493 { background-position: -600px -460px; }
+.emoji-1F494 { background-position: -600px -480px; }
+.emoji-1F495 { background-position: -600px -500px; }
+.emoji-1F496 { background-position: -600px -520px; }
+.emoji-1F497 { background-position: -600px -540px; }
+.emoji-1F498 { background-position: -600px -560px; }
+.emoji-1F499 { background-position: -600px -580px; }
+.emoji-1F49A { background-position: 0px -600px; }
+.emoji-1F49B { background-position: -20px -600px; }
+.emoji-1F49C { background-position: -40px -600px; }
+.emoji-1F49D { background-position: -60px -600px; }
+.emoji-1F49E { background-position: -80px -600px; }
+.emoji-1F49F { background-position: -100px -600px; }
+.emoji-1F4A0 { background-position: -120px -600px; }
+.emoji-1F4A1 { background-position: -140px -600px; }
+.emoji-1F4A2 { background-position: -160px -600px; }
+.emoji-1F4A3 { background-position: -180px -600px; }
+.emoji-1F4A4 { background-position: -200px -600px; }
+.emoji-1F4A5 { background-position: -220px -600px; }
+.emoji-1F4A6 { background-position: -240px -600px; }
+.emoji-1F4A7 { background-position: -260px -600px; }
+.emoji-1F4A8 { background-position: -280px -600px; }
+.emoji-1F4A9 { background-position: -300px -600px; }
+.emoji-1F4AA { background-position: -320px -600px; }
+.emoji-1F4AA-1F3FB { background-position: -340px -600px; }
+.emoji-1F4AA-1F3FC { background-position: -360px -600px; }
+.emoji-1F4AA-1F3FD { background-position: -380px -600px; }
+.emoji-1F4AA-1F3FE { background-position: -400px -600px; }
+.emoji-1F4AA-1F3FF { background-position: -420px -600px; }
+.emoji-1F4AB { background-position: -440px -600px; }
+.emoji-1F4AC { background-position: -460px -600px; }
+.emoji-1F4AD { background-position: -480px -600px; }
+.emoji-1F4AE { background-position: -500px -600px; }
+.emoji-1F4AF { background-position: -520px -600px; }
+.emoji-1F4B0 { background-position: -540px -600px; }
+.emoji-1F4B1 { background-position: -560px -600px; }
+.emoji-1F4B2 { background-position: -580px -600px; }
+.emoji-1F4B3 { background-position: -600px -600px; }
+.emoji-1F4B4 { background-position: -620px 0px; }
+.emoji-1F4B5 { background-position: -620px -20px; }
+.emoji-1F4B6 { background-position: -620px -40px; }
+.emoji-1F4B7 { background-position: -620px -60px; }
+.emoji-1F4B8 { background-position: -620px -80px; }
+.emoji-1F4B9 { background-position: -620px -100px; }
+.emoji-1F4BA { background-position: -620px -120px; }
+.emoji-1F4BB { background-position: -620px -140px; }
+.emoji-1F4BC { background-position: -620px -160px; }
+.emoji-1F4BD { background-position: -620px -180px; }
+.emoji-1F4BE { background-position: -620px -200px; }
+.emoji-1F4BF { background-position: -620px -220px; }
+.emoji-1F4C0 { background-position: -620px -240px; }
+.emoji-1F4C1 { background-position: -620px -260px; }
+.emoji-1F4C2 { background-position: -620px -280px; }
+.emoji-1F4C3 { background-position: -620px -300px; }
+.emoji-1F4C4 { background-position: -620px -320px; }
+.emoji-1F4C5 { background-position: -620px -340px; }
+.emoji-1F4C6 { background-position: -620px -360px; }
+.emoji-1F4C7 { background-position: -620px -380px; }
+.emoji-1F4C8 { background-position: -620px -400px; }
+.emoji-1F4C9 { background-position: -620px -420px; }
+.emoji-1F4CA { background-position: -620px -440px; }
+.emoji-1F4CB { background-position: -620px -460px; }
+.emoji-1F4CC { background-position: -620px -480px; }
+.emoji-1F4CD { background-position: -620px -500px; }
+.emoji-1F4CE { background-position: -620px -520px; }
+.emoji-1F4CF { background-position: -620px -540px; }
+.emoji-1F4D0 { background-position: -620px -560px; }
+.emoji-1F4D1 { background-position: -620px -580px; }
+.emoji-1F4D2 { background-position: -620px -600px; }
+.emoji-1F4D3 { background-position: 0px -620px; }
+.emoji-1F4D4 { background-position: -20px -620px; }
+.emoji-1F4D5 { background-position: -40px -620px; }
+.emoji-1F4D6 { background-position: -60px -620px; }
+.emoji-1F4D7 { background-position: -80px -620px; }
+.emoji-1F4D8 { background-position: -100px -620px; }
+.emoji-1F4D9 { background-position: -120px -620px; }
+.emoji-1F4DA { background-position: -140px -620px; }
+.emoji-1F4DB { background-position: -160px -620px; }
+.emoji-1F4DC { background-position: -180px -620px; }
+.emoji-1F4DD { background-position: -200px -620px; }
+.emoji-1F4DE { background-position: -220px -620px; }
+.emoji-1F4DF { background-position: -240px -620px; }
+.emoji-1F4E0 { background-position: -260px -620px; }
+.emoji-1F4E1 { background-position: -280px -620px; }
+.emoji-1F4E2 { background-position: -300px -620px; }
+.emoji-1F4E3 { background-position: -320px -620px; }
+.emoji-1F4E4 { background-position: -340px -620px; }
+.emoji-1F4E5 { background-position: -360px -620px; }
+.emoji-1F4E6 { background-position: -380px -620px; }
+.emoji-1F4E7 { background-position: -400px -620px; }
+.emoji-1F4E8 { background-position: -420px -620px; }
+.emoji-1F4E9 { background-position: -440px -620px; }
+.emoji-1F4EA { background-position: -460px -620px; }
+.emoji-1F4EB { background-position: -480px -620px; }
+.emoji-1F4EC { background-position: -500px -620px; }
+.emoji-1F4ED { background-position: -520px -620px; }
+.emoji-1F4EE { background-position: -540px -620px; }
+.emoji-1F4EF { background-position: -560px -620px; }
+.emoji-1F4F0 { background-position: -580px -620px; }
+.emoji-1F4F1 { background-position: -600px -620px; }
+.emoji-1F4F2 { background-position: -620px -620px; }
+.emoji-1F4F3 { background-position: -640px 0px; }
+.emoji-1F4F4 { background-position: -640px -20px; }
+.emoji-1F4F5 { background-position: -640px -40px; }
+.emoji-1F4F6 { background-position: -640px -60px; }
+.emoji-1F4F7 { background-position: -640px -80px; }
+.emoji-1F4F8 { background-position: -640px -100px; }
+.emoji-1F4F9 { background-position: -640px -120px; }
+.emoji-1F4FA { background-position: -640px -140px; }
+.emoji-1F4FB { background-position: -640px -160px; }
+.emoji-1F4FC { background-position: -640px -180px; }
+.emoji-1F4FD { background-position: -640px -200px; }
+.emoji-1F4FE { background-position: -640px -220px; }
+.emoji-1F4FF { background-position: -640px -240px; }
+.emoji-1F500 { background-position: -640px -260px; }
+.emoji-1F501 { background-position: -640px -280px; }
+.emoji-1F502 { background-position: -640px -300px; }
+.emoji-1F503 { background-position: -640px -320px; }
+.emoji-1F504 { background-position: -640px -340px; }
+.emoji-1F505 { background-position: -640px -360px; }
+.emoji-1F506 { background-position: -640px -380px; }
+.emoji-1F507 { background-position: -640px -400px; }
+.emoji-1F508 { background-position: -640px -420px; }
+.emoji-1F509 { background-position: -640px -440px; }
+.emoji-1F50A { background-position: -640px -460px; }
+.emoji-1F50B { background-position: -640px -480px; }
+.emoji-1F50C { background-position: -640px -500px; }
+.emoji-1F50D { background-position: -640px -520px; }
+.emoji-1F50E { background-position: -640px -540px; }
+.emoji-1F50F { background-position: -640px -560px; }
+.emoji-1F510 { background-position: -640px -580px; }
+.emoji-1F511 { background-position: -640px -600px; }
+.emoji-1F512 { background-position: -640px -620px; }
+.emoji-1F513 { background-position: 0px -640px; }
+.emoji-1F514 { background-position: -20px -640px; }
+.emoji-1F515 { background-position: -40px -640px; }
+.emoji-1F516 { background-position: -60px -640px; }
+.emoji-1F517 { background-position: -80px -640px; }
+.emoji-1F518 { background-position: -100px -640px; }
+.emoji-1F519 { background-position: -120px -640px; }
+.emoji-1F51A { background-position: -140px -640px; }
+.emoji-1F51B { background-position: -160px -640px; }
+.emoji-1F51C { background-position: -180px -640px; }
+.emoji-1F51D { background-position: -200px -640px; }
+.emoji-1F51E { background-position: -220px -640px; }
+.emoji-1F51F { background-position: -240px -640px; }
+.emoji-1F520 { background-position: -260px -640px; }
+.emoji-1F521 { background-position: -280px -640px; }
+.emoji-1F522 { background-position: -300px -640px; }
+.emoji-1F523 { background-position: -320px -640px; }
+.emoji-1F524 { background-position: -340px -640px; }
+.emoji-1F525 { background-position: -360px -640px; }
+.emoji-1F526 { background-position: -380px -640px; }
+.emoji-1F527 { background-position: -400px -640px; }
+.emoji-1F528 { background-position: -420px -640px; }
+.emoji-1F529 { background-position: -440px -640px; }
+.emoji-1F52A { background-position: -460px -640px; }
+.emoji-1F52B { background-position: -480px -640px; }
+.emoji-1F52C { background-position: -500px -640px; }
+.emoji-1F52D { background-position: -520px -640px; }
+.emoji-1F52E { background-position: -540px -640px; }
+.emoji-1F52F { background-position: -560px -640px; }
+.emoji-1F530 { background-position: -580px -640px; }
+.emoji-1F531 { background-position: -600px -640px; }
+.emoji-1F532 { background-position: -620px -640px; }
+.emoji-1F533 { background-position: -640px -640px; }
+.emoji-1F534 { background-position: -660px 0px; }
+.emoji-1F535 { background-position: -660px -20px; }
+.emoji-1F536 { background-position: -660px -40px; }
+.emoji-1F537 { background-position: -660px -60px; }
+.emoji-1F538 { background-position: -660px -80px; }
+.emoji-1F539 { background-position: -660px -100px; }
+.emoji-1F53A { background-position: -660px -120px; }
+.emoji-1F53B { background-position: -660px -140px; }
+.emoji-1F53C { background-position: -660px -160px; }
+.emoji-1F53D { background-position: -660px -180px; }
+.emoji-1F546 { background-position: -660px -200px; }
+.emoji-1F547 { background-position: -660px -220px; }
+.emoji-1F548 { background-position: -660px -240px; }
+.emoji-1F549 { background-position: -660px -260px; }
+.emoji-1F54A { background-position: -660px -280px; }
+.emoji-1F54B { background-position: -660px -300px; }
+.emoji-1F54C { background-position: -660px -320px; }
+.emoji-1F54D { background-position: -660px -340px; }
+.emoji-1F54E { background-position: -660px -360px; }
+.emoji-1F550 { background-position: -660px -380px; }
+.emoji-1F551 { background-position: -660px -400px; }
+.emoji-1F552 { background-position: -660px -420px; }
+.emoji-1F553 { background-position: -660px -440px; }
+.emoji-1F554 { background-position: -660px -460px; }
+.emoji-1F555 { background-position: -660px -480px; }
+.emoji-1F556 { background-position: -660px -500px; }
+.emoji-1F557 { background-position: -660px -520px; }
+.emoji-1F558 { background-position: -660px -540px; }
+.emoji-1F559 { background-position: -660px -560px; }
+.emoji-1F55A { background-position: -660px -580px; }
+.emoji-1F55B { background-position: -660px -600px; }
+.emoji-1F55C { background-position: -660px -620px; }
+.emoji-1F55D { background-position: -660px -640px; }
+.emoji-1F55E { background-position: 0px -660px; }
+.emoji-1F55F { background-position: -20px -660px; }
+.emoji-1F560 { background-position: -40px -660px; }
+.emoji-1F561 { background-position: -60px -660px; }
+.emoji-1F562 { background-position: -80px -660px; }
+.emoji-1F563 { background-position: -100px -660px; }
+.emoji-1F564 { background-position: -120px -660px; }
+.emoji-1F565 { background-position: -140px -660px; }
+.emoji-1F566 { background-position: -160px -660px; }
+.emoji-1F567 { background-position: -180px -660px; }
+.emoji-1F568 { background-position: -200px -660px; }
+.emoji-1F569 { background-position: -220px -660px; }
+.emoji-1F56A { background-position: -240px -660px; }
+.emoji-1F56B { background-position: -260px -660px; }
+.emoji-1F56C { background-position: -280px -660px; }
+.emoji-1F56D { background-position: -300px -660px; }
+.emoji-1F56E { background-position: -320px -660px; }
+.emoji-1F56F { background-position: -340px -660px; }
+.emoji-1F570 { background-position: -360px -660px; }
+.emoji-1F571 { background-position: -380px -660px; }
+.emoji-1F572 { background-position: -400px -660px; }
+.emoji-1F573 { background-position: -420px -660px; }
+.emoji-1F574 { background-position: -440px -660px; }
+.emoji-1F575 { background-position: -460px -660px; }
+.emoji-1F575-1F3FB { background-position: -480px -660px; }
+.emoji-1F575-1F3FC { background-position: -500px -660px; }
+.emoji-1F575-1F3FD { background-position: -520px -660px; }
+.emoji-1F575-1F3FE { background-position: -540px -660px; }
+.emoji-1F575-1F3FF { background-position: -560px -660px; }
+.emoji-1F576 { background-position: -580px -660px; }
+.emoji-1F577 { background-position: -600px -660px; }
+.emoji-1F578 { background-position: -620px -660px; }
+.emoji-1F579 { background-position: -640px -660px; }
+.emoji-1F57B { background-position: -660px -660px; }
+.emoji-1F57E { background-position: -680px 0px; }
+.emoji-1F57F { background-position: -680px -20px; }
+.emoji-1F581 { background-position: -680px -40px; }
+.emoji-1F582 { background-position: -680px -60px; }
+.emoji-1F583 { background-position: -680px -80px; }
+.emoji-1F585 { background-position: -680px -100px; }
+.emoji-1F586 { background-position: -680px -120px; }
+.emoji-1F587 { background-position: -680px -140px; }
+.emoji-1F588 { background-position: -680px -160px; }
+.emoji-1F589 { background-position: -680px -180px; }
+.emoji-1F58A { background-position: -680px -200px; }
+.emoji-1F58B { background-position: -680px -220px; }
+.emoji-1F58C { background-position: -680px -240px; }
+.emoji-1F58D { background-position: -680px -260px; }
+.emoji-1F58E { background-position: -680px -280px; }
+.emoji-1F58F { background-position: -680px -300px; }
+.emoji-1F590 { background-position: -680px -320px; }
+.emoji-1F590-1F3FB { background-position: -680px -340px; }
+.emoji-1F590-1F3FC { background-position: -680px -360px; }
+.emoji-1F590-1F3FD { background-position: -680px -380px; }
+.emoji-1F590-1F3FE { background-position: -680px -400px; }
+.emoji-1F590-1F3FF { background-position: -680px -420px; }
+.emoji-1F591 { background-position: -680px -440px; }
+.emoji-1F592 { background-position: -680px -460px; }
+.emoji-1F593 { background-position: -680px -480px; }
+.emoji-1F594 { background-position: -680px -500px; }
+.emoji-1F595 { background-position: -680px -520px; }
+.emoji-1F595-1F3FB { background-position: -680px -540px; }
+.emoji-1F595-1F3FC { background-position: -680px -560px; }
+.emoji-1F595-1F3FD { background-position: -680px -580px; }
+.emoji-1F595-1F3FE { background-position: -680px -600px; }
+.emoji-1F595-1F3FF { background-position: -680px -620px; }
+.emoji-1F596 { background-position: -680px -640px; }
+.emoji-1F596-1F3FB { background-position: -680px -660px; }
+.emoji-1F596-1F3FC { background-position: 0px -680px; }
+.emoji-1F596-1F3FD { background-position: -20px -680px; }
+.emoji-1F596-1F3FE { background-position: -40px -680px; }
+.emoji-1F596-1F3FF { background-position: -60px -680px; }
+.emoji-1F597 { background-position: -80px -680px; }
+.emoji-1F598 { background-position: -100px -680px; }
+.emoji-1F599 { background-position: -120px -680px; }
+.emoji-1F59E { background-position: -140px -680px; }
+.emoji-1F59F { background-position: -160px -680px; }
+.emoji-1F5A5 { background-position: -180px -680px; }
+.emoji-1F5A6 { background-position: -200px -680px; }
+.emoji-1F5A7 { background-position: -220px -680px; }
+.emoji-1F5A8 { background-position: -240px -680px; }
+.emoji-1F5A9 { background-position: -260px -680px; }
+.emoji-1F5AA { background-position: -280px -680px; }
+.emoji-1F5AB { background-position: -300px -680px; }
+.emoji-1F5AD { background-position: -320px -680px; }
+.emoji-1F5AE { background-position: -340px -680px; }
+.emoji-1F5AF { background-position: -360px -680px; }
+.emoji-1F5B1 { background-position: -380px -680px; }
+.emoji-1F5B2 { background-position: -400px -680px; }
+.emoji-1F5B3 { background-position: -420px -680px; }
+.emoji-1F5B4 { background-position: -440px -680px; }
+.emoji-1F5B8 { background-position: -460px -680px; }
+.emoji-1F5B9 { background-position: -480px -680px; }
+.emoji-1F5BC { background-position: -500px -680px; }
+.emoji-1F5BD { background-position: -520px -680px; }
+.emoji-1F5BE { background-position: -540px -680px; }
+.emoji-1F5C0 { background-position: -560px -680px; }
+.emoji-1F5C1 { background-position: -580px -680px; }
+.emoji-1F5C2 { background-position: -600px -680px; }
+.emoji-1F5C3 { background-position: -620px -680px; }
+.emoji-1F5C4 { background-position: -640px -680px; }
+.emoji-1F5C6 { background-position: -660px -680px; }
+.emoji-1F5C7 { background-position: -680px -680px; }
+.emoji-1F5C9 { background-position: -700px 0px; }
+.emoji-1F5CA { background-position: -700px -20px; }
+.emoji-1F5CE { background-position: -700px -40px; }
+.emoji-1F5CF { background-position: -700px -60px; }
+.emoji-1F5D0 { background-position: -700px -80px; }
+.emoji-1F5D1 { background-position: -700px -100px; }
+.emoji-1F5D2 { background-position: -700px -120px; }
+.emoji-1F5D3 { background-position: -700px -140px; }
+.emoji-1F5D4 { background-position: -700px -160px; }
+.emoji-1F5D8 { background-position: -700px -180px; }
+.emoji-1F5D9 { background-position: -700px -200px; }
+.emoji-1F5DC { background-position: -700px -220px; }
+.emoji-1F5DD { background-position: -700px -240px; }
+.emoji-1F5DE { background-position: -700px -260px; }
+.emoji-1F5E0 { background-position: -700px -280px; }
+.emoji-1F5E1 { background-position: -700px -300px; }
+.emoji-1F5E2 { background-position: -700px -320px; }
+.emoji-1F5E3 { background-position: -700px -340px; }
+.emoji-1F5E8 { background-position: -700px -360px; }
+.emoji-1F5E9 { background-position: -700px -380px; }
+.emoji-1F5EA { background-position: -700px -400px; }
+.emoji-1F5EB { background-position: -700px -420px; }
+.emoji-1F5EC { background-position: -700px -440px; }
+.emoji-1F5ED { background-position: -700px -460px; }
+.emoji-1F5EE { background-position: -700px -480px; }
+.emoji-1F5EF { background-position: -700px -500px; }
+.emoji-1F5F0 { background-position: -700px -520px; }
+.emoji-1F5F1 { background-position: -700px -540px; }
+.emoji-1F5F2 { background-position: -700px -560px; }
+.emoji-1F5F3 { background-position: -700px -580px; }
+.emoji-1F5F4 { background-position: -700px -600px; }
+.emoji-1F5F5 { background-position: -700px -620px; }
+.emoji-1F5F8 { background-position: -700px -640px; }
+.emoji-1F5F9 { background-position: -700px -660px; }
+.emoji-1F5FA { background-position: -700px -680px; }
+.emoji-1F5FB { background-position: 0px -700px; }
+.emoji-1F5FC { background-position: -20px -700px; }
+.emoji-1F5FD { background-position: -40px -700px; }
+.emoji-1F5FE { background-position: -60px -700px; }
+.emoji-1F5FF { background-position: -80px -700px; }
+.emoji-1F600 { background-position: -100px -700px; }
+.emoji-1F601 { background-position: -120px -700px; }
+.emoji-1F602 { background-position: -140px -700px; }
+.emoji-1F603 { background-position: -160px -700px; }
+.emoji-1F604 { background-position: -180px -700px; }
+.emoji-1F605 { background-position: -200px -700px; }
+.emoji-1F606 { background-position: -220px -700px; }
+.emoji-1F607 { background-position: -240px -700px; }
+.emoji-1F608 { background-position: -260px -700px; }
+.emoji-1F609 { background-position: -280px -700px; }
+.emoji-1F60A { background-position: -300px -700px; }
+.emoji-1F60B { background-position: -320px -700px; }
+.emoji-1F60C { background-position: -340px -700px; }
+.emoji-1F60D { background-position: -360px -700px; }
+.emoji-1F60E { background-position: -380px -700px; }
+.emoji-1F60F { background-position: -400px -700px; }
+.emoji-1F610 { background-position: -420px -700px; }
+.emoji-1F611 { background-position: -440px -700px; }
+.emoji-1F612 { background-position: -460px -700px; }
+.emoji-1F613 { background-position: -480px -700px; }
+.emoji-1F614 { background-position: -500px -700px; }
+.emoji-1F615 { background-position: -520px -700px; }
+.emoji-1F616 { background-position: -540px -700px; }
+.emoji-1F617 { background-position: -560px -700px; }
+.emoji-1F618 { background-position: -580px -700px; }
+.emoji-1F619 { background-position: -600px -700px; }
+.emoji-1F61A { background-position: -620px -700px; }
+.emoji-1F61B { background-position: -640px -700px; }
+.emoji-1F61C { background-position: -660px -700px; }
+.emoji-1F61D { background-position: -680px -700px; }
+.emoji-1F61E { background-position: -700px -700px; }
+.emoji-1F61F { background-position: -720px 0px; }
+.emoji-1F620 { background-position: -720px -20px; }
+.emoji-1F621 { background-position: -720px -40px; }
+.emoji-1F622 { background-position: -720px -60px; }
+.emoji-1F623 { background-position: -720px -80px; }
+.emoji-1F624 { background-position: -720px -100px; }
+.emoji-1F625 { background-position: -720px -120px; }
+.emoji-1F626 { background-position: -720px -140px; }
+.emoji-1F627 { background-position: -720px -160px; }
+.emoji-1F628 { background-position: -720px -180px; }
+.emoji-1F629 { background-position: -720px -200px; }
+.emoji-1F62A { background-position: -720px -220px; }
+.emoji-1F62B { background-position: -720px -240px; }
+.emoji-1F62C { background-position: -720px -260px; }
+.emoji-1F62D { background-position: -720px -280px; }
+.emoji-1F62E { background-position: -720px -300px; }
+.emoji-1F62F { background-position: -720px -320px; }
+.emoji-1F630 { background-position: -720px -340px; }
+.emoji-1F631 { background-position: -720px -360px; }
+.emoji-1F632 { background-position: -720px -380px; }
+.emoji-1F633 { background-position: -720px -400px; }
+.emoji-1F634 { background-position: -720px -420px; }
+.emoji-1F635 { background-position: -720px -440px; }
+.emoji-1F636 { background-position: -720px -460px; }
+.emoji-1F637 { background-position: -720px -480px; }
+.emoji-1F638 { background-position: -720px -500px; }
+.emoji-1F639 { background-position: -720px -520px; }
+.emoji-1F63A { background-position: -720px -540px; }
+.emoji-1F63B { background-position: -720px -560px; }
+.emoji-1F63C { background-position: -720px -580px; }
+.emoji-1F63D { background-position: -720px -600px; }
+.emoji-1F63E { background-position: -720px -620px; }
+.emoji-1F63F { background-position: -720px -640px; }
+.emoji-1F640 { background-position: -720px -660px; }
+.emoji-1F641 { background-position: -720px -680px; }
+.emoji-1F642 { background-position: -720px -700px; }
+.emoji-1F643 { background-position: 0px -720px; }
+.emoji-1F644 { background-position: -20px -720px; }
+.emoji-1F645 { background-position: -40px -720px; }
+.emoji-1F645-1F3FB { background-position: -60px -720px; }
+.emoji-1F645-1F3FC { background-position: -80px -720px; }
+.emoji-1F645-1F3FD { background-position: -100px -720px; }
+.emoji-1F645-1F3FE { background-position: -120px -720px; }
+.emoji-1F645-1F3FF { background-position: -140px -720px; }
+.emoji-1F646 { background-position: -160px -720px; }
+.emoji-1F646-1F3FB { background-position: -180px -720px; }
+.emoji-1F646-1F3FC { background-position: -200px -720px; }
+.emoji-1F646-1F3FD { background-position: -220px -720px; }
+.emoji-1F646-1F3FE { background-position: -240px -720px; }
+.emoji-1F646-1F3FF { background-position: -260px -720px; }
+.emoji-1F647 { background-position: -280px -720px; }
+.emoji-1F647-1F3FB { background-position: -300px -720px; }
+.emoji-1F647-1F3FC { background-position: -320px -720px; }
+.emoji-1F647-1F3FD { background-position: -340px -720px; }
+.emoji-1F647-1F3FE { background-position: -360px -720px; }
+.emoji-1F647-1F3FF { background-position: -380px -720px; }
+.emoji-1F648 { background-position: -400px -720px; }
+.emoji-1F649 { background-position: -420px -720px; }
+.emoji-1F64A { background-position: -440px -720px; }
+.emoji-1F64B { background-position: -460px -720px; }
+.emoji-1F64B-1F3FB { background-position: -480px -720px; }
+.emoji-1F64B-1F3FC { background-position: -500px -720px; }
+.emoji-1F64B-1F3FD { background-position: -520px -720px; }
+.emoji-1F64B-1F3FE { background-position: -540px -720px; }
+.emoji-1F64B-1F3FF { background-position: -560px -720px; }
+.emoji-1F64C { background-position: -580px -720px; }
+.emoji-1F64C-1F3FB { background-position: -600px -720px; }
+.emoji-1F64C-1F3FC { background-position: -620px -720px; }
+.emoji-1F64C-1F3FD { background-position: -640px -720px; }
+.emoji-1F64C-1F3FE { background-position: -660px -720px; }
+.emoji-1F64C-1F3FF { background-position: -680px -720px; }
+.emoji-1F64D { background-position: -700px -720px; }
+.emoji-1F64D-1F3FB { background-position: -720px -720px; }
+.emoji-1F64D-1F3FC { background-position: -740px 0px; }
+.emoji-1F64D-1F3FD { background-position: -740px -20px; }
+.emoji-1F64D-1F3FE { background-position: -740px -40px; }
+.emoji-1F64D-1F3FF { background-position: -740px -60px; }
+.emoji-1F64E { background-position: -740px -80px; }
+.emoji-1F64E-1F3FB { background-position: -740px -100px; }
+.emoji-1F64E-1F3FC { background-position: -740px -120px; }
+.emoji-1F64E-1F3FD { background-position: -740px -140px; }
+.emoji-1F64E-1F3FE { background-position: -740px -160px; }
+.emoji-1F64E-1F3FF { background-position: -740px -180px; }
+.emoji-1F64F { background-position: -740px -200px; }
+.emoji-1F64F-1F3FB { background-position: -740px -220px; }
+.emoji-1F64F-1F3FC { background-position: -740px -240px; }
+.emoji-1F64F-1F3FD { background-position: -740px -260px; }
+.emoji-1F64F-1F3FE { background-position: -740px -280px; }
+.emoji-1F64F-1F3FF { background-position: -740px -300px; }
+.emoji-1F680 { background-position: -740px -320px; }
+.emoji-1F681 { background-position: -740px -340px; }
+.emoji-1F682 { background-position: -740px -360px; }
+.emoji-1F683 { background-position: -740px -380px; }
+.emoji-1F684 { background-position: -740px -400px; }
+.emoji-1F685 { background-position: -740px -420px; }
+.emoji-1F686 { background-position: -740px -440px; }
+.emoji-1F687 { background-position: -740px -460px; }
+.emoji-1F688 { background-position: -740px -480px; }
+.emoji-1F689 { background-position: -740px -500px; }
+.emoji-1F68A { background-position: -740px -520px; }
+.emoji-1F68B { background-position: -740px -540px; }
+.emoji-1F68C { background-position: -740px -560px; }
+.emoji-1F68D { background-position: -740px -580px; }
+.emoji-1F68E { background-position: -740px -600px; }
+.emoji-1F68F { background-position: -740px -620px; }
+.emoji-1F690 { background-position: -740px -640px; }
+.emoji-1F691 { background-position: -740px -660px; }
+.emoji-1F692 { background-position: -740px -680px; }
+.emoji-1F693 { background-position: -740px -700px; }
+.emoji-1F694 { background-position: -740px -720px; }
+.emoji-1F695 { background-position: 0px -740px; }
+.emoji-1F696 { background-position: -20px -740px; }
+.emoji-1F697 { background-position: -40px -740px; }
+.emoji-1F698 { background-position: -60px -740px; }
+.emoji-1F699 { background-position: -80px -740px; }
+.emoji-1F69A { background-position: -100px -740px; }
+.emoji-1F69B { background-position: -120px -740px; }
+.emoji-1F69C { background-position: -140px -740px; }
+.emoji-1F69D { background-position: -160px -740px; }
+.emoji-1F69E { background-position: -180px -740px; }
+.emoji-1F69F { background-position: -200px -740px; }
+.emoji-1F6A0 { background-position: -220px -740px; }
+.emoji-1F6A1 { background-position: -240px -740px; }
+.emoji-1F6A2 { background-position: -260px -740px; }
+.emoji-1F6A3 { background-position: -280px -740px; }
+.emoji-1F6A3-1F3FB { background-position: -300px -740px; }
+.emoji-1F6A3-1F3FC { background-position: -320px -740px; }
+.emoji-1F6A3-1F3FD { background-position: -340px -740px; }
+.emoji-1F6A3-1F3FE { background-position: -360px -740px; }
+.emoji-1F6A3-1F3FF { background-position: -380px -740px; }
+.emoji-1F6A4 { background-position: -400px -740px; }
+.emoji-1F6A5 { background-position: -420px -740px; }
+.emoji-1F6A6 { background-position: -440px -740px; }
+.emoji-1F6A7 { background-position: -460px -740px; }
+.emoji-1F6A8 { background-position: -480px -740px; }
+.emoji-1F6A9 { background-position: -500px -740px; }
+.emoji-1F6AA { background-position: -520px -740px; }
+.emoji-1F6AB { background-position: -540px -740px; }
+.emoji-1F6AC { background-position: -560px -740px; }
+.emoji-1F6AD { background-position: -580px -740px; }
+.emoji-1F6AE { background-position: -600px -740px; }
+.emoji-1F6AF { background-position: -620px -740px; }
+.emoji-1F6B0 { background-position: -640px -740px; }
+.emoji-1F6B1 { background-position: -660px -740px; }
+.emoji-1F6B2 { background-position: -680px -740px; }
+.emoji-1F6B3 { background-position: -700px -740px; }
+.emoji-1F6B4 { background-position: -720px -740px; }
+.emoji-1F6B4-1F3FB { background-position: -740px -740px; }
+.emoji-1F6B4-1F3FC { background-position: -760px 0px; }
+.emoji-1F6B4-1F3FD { background-position: -760px -20px; }
+.emoji-1F6B4-1F3FE { background-position: -760px -40px; }
+.emoji-1F6B4-1F3FF { background-position: -760px -60px; }
+.emoji-1F6B5 { background-position: -760px -80px; }
+.emoji-1F6B5-1F3FB { background-position: -760px -100px; }
+.emoji-1F6B5-1F3FC { background-position: -760px -120px; }
+.emoji-1F6B5-1F3FD { background-position: -760px -140px; }
+.emoji-1F6B5-1F3FE { background-position: -760px -160px; }
+.emoji-1F6B5-1F3FF { background-position: -760px -180px; }
+.emoji-1F6B6 { background-position: -760px -200px; }
+.emoji-1F6B6-1F3FB { background-position: -760px -220px; }
+.emoji-1F6B6-1F3FC { background-position: -760px -240px; }
+.emoji-1F6B6-1F3FD { background-position: -760px -260px; }
+.emoji-1F6B6-1F3FE { background-position: -760px -280px; }
+.emoji-1F6B6-1F3FF { background-position: -760px -300px; }
+.emoji-1F6B7 { background-position: -760px -320px; }
+.emoji-1F6B8 { background-position: -760px -340px; }
+.emoji-1F6B9 { background-position: -760px -360px; }
+.emoji-1F6BA { background-position: -760px -380px; }
+.emoji-1F6BB { background-position: -760px -400px; }
+.emoji-1F6BC { background-position: -760px -420px; }
+.emoji-1F6BD { background-position: -760px -440px; }
+.emoji-1F6BE { background-position: -760px -460px; }
+.emoji-1F6BF { background-position: -760px -480px; }
+.emoji-1F6C0 { background-position: -760px -500px; }
+.emoji-1F6C0-1F3FB { background-position: -760px -520px; }
+.emoji-1F6C0-1F3FC { background-position: -760px -540px; }
+.emoji-1F6C0-1F3FD { background-position: -760px -560px; }
+.emoji-1F6C0-1F3FE { background-position: -760px -580px; }
+.emoji-1F6C0-1F3FF { background-position: -760px -600px; }
+.emoji-1F6C1 { background-position: -760px -620px; }
+.emoji-1F6C2 { background-position: -760px -640px; }
+.emoji-1F6C3 { background-position: -760px -660px; }
+.emoji-1F6C4 { background-position: -760px -680px; }
+.emoji-1F6C5 { background-position: -760px -700px; }
+.emoji-1F6C6 { background-position: -760px -720px; }
+.emoji-1F6C7 { background-position: -760px -740px; }
+.emoji-1F6C8 { background-position: 0px -760px; }
+.emoji-1F6C9 { background-position: -20px -760px; }
+.emoji-1F6CA { background-position: -40px -760px; }
+.emoji-1F6CB { background-position: -60px -760px; }
+.emoji-1F6CC { background-position: -80px -760px; }
+.emoji-1F6CD { background-position: -100px -760px; }
+.emoji-1F6CE { background-position: -120px -760px; }
+.emoji-1F6CF { background-position: -140px -760px; }
+.emoji-1F6D0 { background-position: -160px -760px; }
+.emoji-1F6E0 { background-position: -180px -760px; }
+.emoji-1F6E1 { background-position: -200px -760px; }
+.emoji-1F6E2 { background-position: -220px -760px; }
+.emoji-1F6E3 { background-position: -240px -760px; }
+.emoji-1F6E4 { background-position: -260px -760px; }
+.emoji-1F6E5 { background-position: -280px -760px; }
+.emoji-1F6E6 { background-position: -300px -760px; }
+.emoji-1F6E7 { background-position: -320px -760px; }
+.emoji-1F6E8 { background-position: -340px -760px; }
+.emoji-1F6E9 { background-position: -360px -760px; }
+.emoji-1F6EA { background-position: -380px -760px; }
+.emoji-1F6EB { background-position: -400px -760px; }
+.emoji-1F6EC { background-position: -420px -760px; }
+.emoji-1F6F0 { background-position: -440px -760px; }
+.emoji-1F6F1 { background-position: -460px -760px; }
+.emoji-1F6F2 { background-position: -480px -760px; }
+.emoji-1F6F3 { background-position: -500px -760px; }
+.emoji-1F910 { background-position: -520px -760px; }
+.emoji-1F911 { background-position: -540px -760px; }
+.emoji-1F912 { background-position: -560px -760px; }
+.emoji-1F913 { background-position: -580px -760px; }
+.emoji-1F914 { background-position: -600px -760px; }
+.emoji-1F915 { background-position: -620px -760px; }
+.emoji-1F916 { background-position: -640px -760px; }
+.emoji-1F917 { background-position: -660px -760px; }
+.emoji-1F918 { background-position: -680px -760px; }
+.emoji-1F918-1F3FB { background-position: -700px -760px; }
+.emoji-1F918-1F3FC { background-position: -720px -760px; }
+.emoji-1F918-1F3FD { background-position: -740px -760px; }
+.emoji-1F918-1F3FE { background-position: -760px -760px; }
+.emoji-1F918-1F3FF { background-position: -780px 0px; }
+.emoji-1F980 { background-position: -780px -20px; }
+.emoji-1F981 { background-position: -780px -40px; }
+.emoji-1F982 { background-position: -780px -60px; }
+.emoji-1F983 { background-position: -780px -80px; }
+.emoji-1F984 { background-position: -780px -100px; }
+.emoji-1F9C0 { background-position: -780px -120px; }
+.emoji-203C { background-position: -780px -140px; }
+.emoji-2049 { background-position: -780px -160px; }
+.emoji-2122 { background-position: -780px -180px; }
+.emoji-2139 { background-position: -780px -200px; }
+.emoji-2194 { background-position: -780px -220px; }
+.emoji-2195 { background-position: -780px -240px; }
+.emoji-2196 { background-position: -780px -260px; }
+.emoji-2197 { background-position: -780px -280px; }
+.emoji-2198 { background-position: -780px -300px; }
+.emoji-2199 { background-position: -780px -320px; }
+.emoji-21A9 { background-position: -780px -340px; }
+.emoji-21AA { background-position: -780px -360px; }
+.emoji-231A { background-position: -780px -380px; }
+.emoji-231B { background-position: -780px -400px; }
+.emoji-2328 { background-position: -780px -420px; }
+.emoji-23E9 { background-position: -780px -440px; }
+.emoji-23EA { background-position: -780px -460px; }
+.emoji-23EB { background-position: -780px -480px; }
+.emoji-23EC { background-position: -780px -500px; }
+.emoji-23ED { background-position: -780px -520px; }
+.emoji-23EE { background-position: -780px -540px; }
+.emoji-23EF { background-position: -780px -560px; }
+.emoji-23F0 { background-position: -780px -580px; }
+.emoji-23F1 { background-position: -780px -600px; }
+.emoji-23F2 { background-position: -780px -620px; }
+.emoji-23F3 { background-position: -780px -640px; }
+.emoji-23F8 { background-position: -780px -660px; }
+.emoji-23F9 { background-position: -780px -680px; }
+.emoji-23FA { background-position: -780px -700px; }
+.emoji-24C2 { background-position: -780px -720px; }
+.emoji-25AA { background-position: -780px -740px; }
+.emoji-25AB { background-position: -780px -760px; }
+.emoji-25B6 { background-position: 0px -780px; }
+.emoji-25C0 { background-position: -20px -780px; }
+.emoji-25FB { background-position: -40px -780px; }
+.emoji-25FC { background-position: -60px -780px; }
+.emoji-25FD { background-position: -80px -780px; }
+.emoji-25FE { background-position: -100px -780px; }
+.emoji-2600 { background-position: -120px -780px; }
+.emoji-2601 { background-position: -140px -780px; }
+.emoji-2602 { background-position: -160px -780px; }
+.emoji-2603 { background-position: -180px -780px; }
+.emoji-2604 { background-position: -200px -780px; }
+.emoji-260E { background-position: -220px -780px; }
+.emoji-2611 { background-position: -240px -780px; }
+.emoji-2614 { background-position: -260px -780px; }
+.emoji-2615 { background-position: -280px -780px; }
+.emoji-2618 { background-position: -300px -780px; }
+.emoji-261D { background-position: -320px -780px; }
+.emoji-261D-1F3FB { background-position: -340px -780px; }
+.emoji-261D-1F3FC { background-position: -360px -780px; }
+.emoji-261D-1F3FD { background-position: -380px -780px; }
+.emoji-261D-1F3FE { background-position: -400px -780px; }
+.emoji-261D-1F3FF { background-position: -420px -780px; }
+.emoji-2620 { background-position: -440px -780px; }
+.emoji-2622 { background-position: -460px -780px; }
+.emoji-2623 { background-position: -480px -780px; }
+.emoji-2626 { background-position: -500px -780px; }
+.emoji-262A { background-position: -520px -780px; }
+.emoji-262E { background-position: -540px -780px; }
+.emoji-262F { background-position: -560px -780px; }
+.emoji-2638 { background-position: -580px -780px; }
+.emoji-2639 { background-position: -600px -780px; }
+.emoji-263A { background-position: -620px -780px; }
+.emoji-2648 { background-position: -640px -780px; }
+.emoji-2649 { background-position: -660px -780px; }
+.emoji-264A { background-position: -680px -780px; }
+.emoji-264B { background-position: -700px -780px; }
+.emoji-264C { background-position: -720px -780px; }
+.emoji-264D { background-position: -740px -780px; }
+.emoji-264E { background-position: -760px -780px; }
+.emoji-264F { background-position: -780px -780px; }
+.emoji-2650 { background-position: -800px 0px; }
+.emoji-2651 { background-position: -800px -20px; }
+.emoji-2652 { background-position: -800px -40px; }
+.emoji-2653 { background-position: -800px -60px; }
+.emoji-2660 { background-position: -800px -80px; }
+.emoji-2663 { background-position: -800px -100px; }
+.emoji-2665 { background-position: -800px -120px; }
+.emoji-2666 { background-position: -800px -140px; }
+.emoji-2668 { background-position: -800px -160px; }
+.emoji-267B { background-position: -800px -180px; }
+.emoji-267F { background-position: -800px -200px; }
+.emoji-2692 { background-position: -800px -220px; }
+.emoji-2693 { background-position: -800px -240px; }
+.emoji-2694 { background-position: -800px -260px; }
+.emoji-2696 { background-position: -800px -280px; }
+.emoji-2697 { background-position: -800px -300px; }
+.emoji-2699 { background-position: -800px -320px; }
+.emoji-269B { background-position: -800px -340px; }
+.emoji-269C { background-position: -800px -360px; }
+.emoji-26A0 { background-position: -800px -380px; }
+.emoji-26A1 { background-position: -800px -400px; }
+.emoji-26AA { background-position: -800px -420px; }
+.emoji-26AB { background-position: -800px -440px; }
+.emoji-26B0 { background-position: -800px -460px; }
+.emoji-26B1 { background-position: -800px -480px; }
+.emoji-26BD { background-position: -800px -500px; }
+.emoji-26BE { background-position: -800px -520px; }
+.emoji-26C4 { background-position: -800px -540px; }
+.emoji-26C5 { background-position: -800px -560px; }
+.emoji-26C8 { background-position: -800px -580px; }
+.emoji-26CE { background-position: -800px -600px; }
+.emoji-26CF { background-position: -800px -620px; }
+.emoji-26D1 { background-position: -800px -640px; }
+.emoji-26D3 { background-position: -800px -660px; }
+.emoji-26D4 { background-position: -800px -680px; }
+.emoji-26E9 { background-position: -800px -700px; }
+.emoji-26EA { background-position: -800px -720px; }
+.emoji-26F0 { background-position: -800px -740px; }
+.emoji-26F1 { background-position: -800px -760px; }
+.emoji-26F2 { background-position: -800px -780px; }
+.emoji-26F3 { background-position: 0px -800px; }
+.emoji-26F4 { background-position: -20px -800px; }
+.emoji-26F5 { background-position: -40px -800px; }
+.emoji-26F7 { background-position: -60px -800px; }
+.emoji-26F8 { background-position: -80px -800px; }
+.emoji-26F9 { background-position: -100px -800px; }
+.emoji-26F9-1F3FB { background-position: -120px -800px; }
+.emoji-26F9-1F3FC { background-position: -140px -800px; }
+.emoji-26F9-1F3FD { background-position: -160px -800px; }
+.emoji-26F9-1F3FE { background-position: -180px -800px; }
+.emoji-26F9-1F3FF { background-position: -200px -800px; }
+.emoji-26FA { background-position: -220px -800px; }
+.emoji-26FD { background-position: -240px -800px; }
+.emoji-2702 { background-position: -260px -800px; }
+.emoji-2705 { background-position: -280px -800px; }
+.emoji-2708 { background-position: -300px -800px; }
+.emoji-2709 { background-position: -320px -800px; }
+.emoji-270A { background-position: -340px -800px; }
+.emoji-270A-1F3FB { background-position: -360px -800px; }
+.emoji-270A-1F3FC { background-position: -380px -800px; }
+.emoji-270A-1F3FD { background-position: -400px -800px; }
+.emoji-270A-1F3FE { background-position: -420px -800px; }
+.emoji-270A-1F3FF { background-position: -440px -800px; }
+.emoji-270B { background-position: -460px -800px; }
+.emoji-270B-1F3FB { background-position: -480px -800px; }
+.emoji-270B-1F3FC { background-position: -500px -800px; }
+.emoji-270B-1F3FD { background-position: -520px -800px; }
+.emoji-270B-1F3FE { background-position: -540px -800px; }
+.emoji-270B-1F3FF { background-position: -560px -800px; }
+.emoji-270C { background-position: -580px -800px; }
+.emoji-270C-1F3FB { background-position: -600px -800px; }
+.emoji-270C-1F3FC { background-position: -620px -800px; }
+.emoji-270C-1F3FD { background-position: -640px -800px; }
+.emoji-270C-1F3FE { background-position: -660px -800px; }
+.emoji-270C-1F3FF { background-position: -680px -800px; }
+.emoji-270D { background-position: -700px -800px; }
+.emoji-270D-1F3FB { background-position: -720px -800px; }
+.emoji-270D-1F3FC { background-position: -740px -800px; }
+.emoji-270D-1F3FD { background-position: -760px -800px; }
+.emoji-270D-1F3FE { background-position: -780px -800px; }
+.emoji-270D-1F3FF { background-position: -800px -800px; }
+.emoji-270F { background-position: -820px 0px; }
+.emoji-2712 { background-position: -820px -20px; }
+.emoji-2714 { background-position: -820px -40px; }
+.emoji-2716 { background-position: -820px -60px; }
+.emoji-271D { background-position: -820px -80px; }
+.emoji-2721 { background-position: -820px -100px; }
+.emoji-2728 { background-position: -820px -120px; }
+.emoji-2733 { background-position: -820px -140px; }
+.emoji-2734 { background-position: -820px -160px; }
+.emoji-2744 { background-position: -820px -180px; }
+.emoji-2747 { background-position: -820px -200px; }
+.emoji-274C { background-position: -820px -220px; }
+.emoji-274E { background-position: -820px -240px; }
+.emoji-2753 { background-position: -820px -260px; }
+.emoji-2754 { background-position: -820px -280px; }
+.emoji-2755 { background-position: -820px -300px; }
+.emoji-2757 { background-position: -820px -320px; }
+.emoji-2763 { background-position: -820px -340px; }
+.emoji-2764 { background-position: -820px -360px; }
+.emoji-2795 { background-position: -820px -380px; }
+.emoji-2796 { background-position: -820px -400px; }
+.emoji-2797 { background-position: -820px -420px; }
+.emoji-27A1 { background-position: -820px -440px; }
+.emoji-27B0 { background-position: -820px -460px; }
+.emoji-27BF { background-position: -820px -480px; }
+.emoji-2934 { background-position: -820px -500px; }
+.emoji-2935 { background-position: -820px -520px; }
+.emoji-2B05 { background-position: -820px -540px; }
+.emoji-2B06 { background-position: -820px -560px; }
+.emoji-2B07 { background-position: -820px -580px; }
+.emoji-2B1B { background-position: -820px -600px; }
+.emoji-2B1C { background-position: -820px -620px; }
+.emoji-2B50 { background-position: -820px -640px; }
+.emoji-2B55 { background-position: -820px -660px; }
+.emoji-3030 { background-position: -820px -680px; }
+.emoji-303D { background-position: -820px -700px; }
+.emoji-3297 { background-position: -820px -720px; }
+.emoji-3299 { background-position: -820px -740px; }
-.emoji-icon{
- background-image: image-url("emoji.png");
+.emoji-icon {
+ background-image: image-url('emoji.png');
background-repeat: no-repeat;
-}
+ height: 20px;
+ width: 20px;
-.emoji-0023-20E3 { background-position: 0px 0px; }
-.emoji-0030-20E3 { background-position: -20px 0px; }
-.emoji-0031-20E3 { background-position: -40px 0px; }
-.emoji-0032-20E3 { background-position: -60px 0px; }
-.emoji-0033-20E3 { background-position: -80px 0px; }
-.emoji-0034-20E3 { background-position: -100px 0px; }
-.emoji-0035-20E3 { background-position: -120px 0px; }
-.emoji-0036-20E3 { background-position: -140px 0px; }
-.emoji-0037-20E3 { background-position: -160px 0px; }
-.emoji-0038-20E3 { background-position: -180px 0px; }
-.emoji-0039-20E3 { background-position: -200px 0px; }
-.emoji-00A9 { background-position: -220px 0px; }
-.emoji-00AE { background-position: -240px 0px; }
-.emoji-1F004 { background-position: -260px 0px; }
-.emoji-1F0CF { background-position: -280px 0px; }
-.emoji-1F170 { background-position: -300px 0px; }
-.emoji-1F171 { background-position: -320px 0px; }
-.emoji-1F17E { background-position: -340px 0px; }
-.emoji-1F17F { background-position: -360px 0px; }
-.emoji-1F18E { background-position: -380px 0px; }
-.emoji-1F191 { background-position: -400px 0px; }
-.emoji-1F192 { background-position: -420px 0px; }
-.emoji-1F193 { background-position: -440px 0px; }
-.emoji-1F194 { background-position: -460px 0px; }
-.emoji-1F195 { background-position: -480px 0px; }
-.emoji-1F196 { background-position: -500px 0px; }
-.emoji-1F197 { background-position: -520px 0px; }
-.emoji-1F198 { background-position: -540px 0px; }
-.emoji-1F199 { background-position: -560px 0px; }
-.emoji-1F19A { background-position: -580px 0px; }
-.emoji-1F1E6-1F1E8 { background-position: -600px 0px; }
-.emoji-1F1E6-1F1E9 { background-position: -620px 0px; }
-.emoji-1F1E6-1F1EA { background-position: -640px 0px; }
-.emoji-1F1E6-1F1EB { background-position: -660px 0px; }
-.emoji-1F1E6-1F1EC { background-position: -680px 0px; }
-.emoji-1F1E6-1F1EE { background-position: -700px 0px; }
-.emoji-1F1E6-1F1F1 { background-position: -720px 0px; }
-.emoji-1F1E6-1F1F2 { background-position: -740px 0px; }
-.emoji-1F1E6-1F1F4 { background-position: -760px 0px; }
-.emoji-1F1E6-1F1F7 { background-position: -780px 0px; }
-.emoji-1F1E6-1F1F9 { background-position: -800px 0px; }
-.emoji-1F1E6-1F1FA { background-position: -820px 0px; }
-.emoji-1F1E6-1F1FC { background-position: -840px 0px; }
-.emoji-1F1E6-1F1FF { background-position: -860px 0px; }
-.emoji-1F1E7-1F1E6 { background-position: -880px 0px; }
-.emoji-1F1E7-1F1E7 { background-position: -900px 0px; }
-.emoji-1F1E7-1F1E9 { background-position: -920px 0px; }
-.emoji-1F1E7-1F1EA { background-position: -940px 0px; }
-.emoji-1F1E7-1F1EB { background-position: -960px 0px; }
-.emoji-1F1E7-1F1EC { background-position: -980px 0px; }
-.emoji-1F1E7-1F1ED { background-position: -1000px 0px; }
-.emoji-1F1E7-1F1EE { background-position: -1020px 0px; }
-.emoji-1F1E7-1F1EF { background-position: -1040px 0px; }
-.emoji-1F1E7-1F1F2 { background-position: -1060px 0px; }
-.emoji-1F1E7-1F1F3 { background-position: -1080px 0px; }
-.emoji-1F1E7-1F1F4 { background-position: -1100px 0px; }
-.emoji-1F1E7-1F1F7 { background-position: -1120px 0px; }
-.emoji-1F1E7-1F1F8 { background-position: -1140px 0px; }
-.emoji-1F1E7-1F1F9 { background-position: -1160px 0px; }
-.emoji-1F1E7-1F1FC { background-position: -1180px 0px; }
-.emoji-1F1E7-1F1FE { background-position: -1200px 0px; }
-.emoji-1F1E7-1F1FF { background-position: -1220px 0px; }
-.emoji-1F1E8-1F1E6 { background-position: -1240px 0px; }
-.emoji-1F1E8-1F1E9 { background-position: -1260px 0px; }
-.emoji-1F1E8-1F1EB { background-position: -1280px 0px; }
-.emoji-1F1E8-1F1EC { background-position: -1300px 0px; }
-.emoji-1F1E8-1F1ED { background-position: -1320px 0px; }
-.emoji-1F1E8-1F1EE { background-position: -1340px 0px; }
-.emoji-1F1E8-1F1F1 { background-position: -1360px 0px; }
-.emoji-1F1E8-1F1F2 { background-position: -1380px 0px; }
-.emoji-1F1E8-1F1F3 { background-position: -1400px 0px; }
-.emoji-1F1E8-1F1F4 { background-position: -1420px 0px; }
-.emoji-1F1E8-1F1F7 { background-position: -1440px 0px; }
-.emoji-1F1E8-1F1FA { background-position: -1460px 0px; }
-.emoji-1F1E8-1F1FB { background-position: -1480px 0px; }
-.emoji-1F1E8-1F1FE { background-position: -1500px 0px; }
-.emoji-1F1E8-1F1FF { background-position: -1520px 0px; }
-.emoji-1F1E9-1F1EA { background-position: -1540px 0px; }
-.emoji-1F1E9-1F1EF { background-position: -1560px 0px; }
-.emoji-1F1E9-1F1F0 { background-position: -1580px 0px; }
-.emoji-1F1E9-1F1F2 { background-position: -1600px 0px; }
-.emoji-1F1E9-1F1F4 { background-position: -1620px 0px; }
-.emoji-1F1E9-1F1FF { background-position: -1640px 0px; }
-.emoji-1F1EA-1F1E8 { background-position: -1660px 0px; }
-.emoji-1F1EA-1F1EA { background-position: -1680px 0px; }
-.emoji-1F1EA-1F1EC { background-position: -1700px 0px; }
-.emoji-1F1EA-1F1ED { background-position: -1720px 0px; }
-.emoji-1F1EA-1F1F7 { background-position: -1740px 0px; }
-.emoji-1F1EA-1F1F8 { background-position: -1760px 0px; }
-.emoji-1F1EA-1F1F9 { background-position: -1780px 0px; }
-.emoji-1F1EB-1F1EE { background-position: -1800px 0px; }
-.emoji-1F1EB-1F1EF { background-position: -1820px 0px; }
-.emoji-1F1EB-1F1F0 { background-position: -1840px 0px; }
-.emoji-1F1EB-1F1F2 { background-position: -1860px 0px; }
-.emoji-1F1EB-1F1F4 { background-position: -1880px 0px; }
-.emoji-1F1EB-1F1F7 { background-position: -1900px 0px; }
-.emoji-1F1EC-1F1E6 { background-position: -1920px 0px; }
-.emoji-1F1EC-1F1E7 { background-position: -1940px 0px; }
-.emoji-1F1EC-1F1E9 { background-position: -1960px 0px; }
-.emoji-1F1EC-1F1EA { background-position: -1980px 0px; }
-.emoji-1F1EC-1F1ED { background-position: -2000px 0px; }
-.emoji-1F1EC-1F1EE { background-position: -2020px 0px; }
-.emoji-1F1EC-1F1F1 { background-position: -2040px 0px; }
-.emoji-1F1EC-1F1F2 { background-position: -2060px 0px; }
-.emoji-1F1EC-1F1F3 { background-position: -2080px 0px; }
-.emoji-1F1EC-1F1F6 { background-position: -2100px 0px; }
-.emoji-1F1EC-1F1F7 { background-position: -2120px 0px; }
-.emoji-1F1EC-1F1F9 { background-position: -2140px 0px; }
-.emoji-1F1EC-1F1FA { background-position: -2160px 0px; }
-.emoji-1F1EC-1F1FC { background-position: -2180px 0px; }
-.emoji-1F1EC-1F1FE { background-position: -2200px 0px; }
-.emoji-1F1ED-1F1F0 { background-position: -2220px 0px; }
-.emoji-1F1ED-1F1F3 { background-position: -2240px 0px; }
-.emoji-1F1ED-1F1F7 { background-position: -2260px 0px; }
-.emoji-1F1ED-1F1F9 { background-position: -2280px 0px; }
-.emoji-1F1ED-1F1FA { background-position: -2300px 0px; }
-.emoji-1F1EE-1F1E9 { background-position: -2320px 0px; }
-.emoji-1F1EE-1F1EA { background-position: -2340px 0px; }
-.emoji-1F1EE-1F1F1 { background-position: -2360px 0px; }
-.emoji-1F1EE-1F1F3 { background-position: -2380px 0px; }
-.emoji-1F1EE-1F1F6 { background-position: -2400px 0px; }
-.emoji-1F1EE-1F1F7 { background-position: -2420px 0px; }
-.emoji-1F1EE-1F1F8 { background-position: -2440px 0px; }
-.emoji-1F1EE-1F1F9 { background-position: -2460px 0px; }
-.emoji-1F1EF-1F1EA { background-position: -2480px 0px; }
-.emoji-1F1EF-1F1F2 { background-position: -2500px 0px; }
-.emoji-1F1EF-1F1F4 { background-position: -2520px 0px; }
-.emoji-1F1EF-1F1F5 { background-position: -2540px 0px; }
-.emoji-1F1F0-1F1EA { background-position: -2560px 0px; }
-.emoji-1F1F0-1F1EC { background-position: -2580px 0px; }
-.emoji-1F1F0-1F1ED { background-position: -2600px 0px; }
-.emoji-1F1F0-1F1EE { background-position: -2620px 0px; }
-.emoji-1F1F0-1F1F2 { background-position: -2640px 0px; }
-.emoji-1F1F0-1F1F3 { background-position: -2660px 0px; }
-.emoji-1F1F0-1F1F5 { background-position: -2680px 0px; }
-.emoji-1F1F0-1F1F7 { background-position: -2700px 0px; }
-.emoji-1F1F0-1F1FC { background-position: -2720px 0px; }
-.emoji-1F1F0-1F1FE { background-position: -2740px 0px; }
-.emoji-1F1F0-1F1FF { background-position: -2760px 0px; }
-.emoji-1F1F1-1F1E6 { background-position: -2780px 0px; }
-.emoji-1F1F1-1F1E7 { background-position: -2800px 0px; }
-.emoji-1F1F1-1F1E8 { background-position: -2820px 0px; }
-.emoji-1F1F1-1F1EE { background-position: -2840px 0px; }
-.emoji-1F1F1-1F1F0 { background-position: -2860px 0px; }
-.emoji-1F1F1-1F1F7 { background-position: -2880px 0px; }
-.emoji-1F1F1-1F1F8 { background-position: -2900px 0px; }
-.emoji-1F1F1-1F1F9 { background-position: -2920px 0px; }
-.emoji-1F1F1-1F1FA { background-position: -2940px 0px; }
-.emoji-1F1F1-1F1FB { background-position: -2960px 0px; }
-.emoji-1F1F1-1F1FE { background-position: -2980px 0px; }
-.emoji-1F1F2-1F1E6 { background-position: -3000px 0px; }
-.emoji-1F1F2-1F1E8 { background-position: -3020px 0px; }
-.emoji-1F1F2-1F1E9 { background-position: -3040px 0px; }
-.emoji-1F1F2-1F1EA { background-position: -3060px 0px; }
-.emoji-1F1F2-1F1EC { background-position: -3080px 0px; }
-.emoji-1F1F2-1F1ED { background-position: -3100px 0px; }
-.emoji-1F1F2-1F1F0 { background-position: -3120px 0px; }
-.emoji-1F1F2-1F1F1 { background-position: -3140px 0px; }
-.emoji-1F1F2-1F1F2 { background-position: -3160px 0px; }
-.emoji-1F1F2-1F1F3 { background-position: -3180px 0px; }
-.emoji-1F1F2-1F1F4 { background-position: -3200px 0px; }
-.emoji-1F1F2-1F1F7 { background-position: -3220px 0px; }
-.emoji-1F1F2-1F1F8 { background-position: -3240px 0px; }
-.emoji-1F1F2-1F1F9 { background-position: -3260px 0px; }
-.emoji-1F1F2-1F1FA { background-position: -3280px 0px; }
-.emoji-1F1F2-1F1FB { background-position: -3300px 0px; }
-.emoji-1F1F2-1F1FC { background-position: -3320px 0px; }
-.emoji-1F1F2-1F1FD { background-position: -3340px 0px; }
-.emoji-1F1F2-1F1FE { background-position: -3360px 0px; }
-.emoji-1F1F2-1F1FF { background-position: -3380px 0px; }
-.emoji-1F1F3-1F1E6 { background-position: -3400px 0px; }
-.emoji-1F1F3-1F1E8 { background-position: -3420px 0px; }
-.emoji-1F1F3-1F1EA { background-position: -3440px 0px; }
-.emoji-1F1F3-1F1EC { background-position: -3460px 0px; }
-.emoji-1F1F3-1F1EE { background-position: -3480px 0px; }
-.emoji-1F1F3-1F1F1 { background-position: -3500px 0px; }
-.emoji-1F1F3-1F1F4 { background-position: -3520px 0px; }
-.emoji-1F1F3-1F1F5 { background-position: -3540px 0px; }
-.emoji-1F1F3-1F1F7 { background-position: -3560px 0px; }
-.emoji-1F1F3-1F1FA { background-position: -3580px 0px; }
-.emoji-1F1F3-1F1FF { background-position: -3600px 0px; }
-.emoji-1F1F4-1F1F2 { background-position: -3620px 0px; }
-.emoji-1F1F5-1F1E6 { background-position: -3640px 0px; }
-.emoji-1F1F5-1F1EA { background-position: -3660px 0px; }
-.emoji-1F1F5-1F1EB { background-position: -3680px 0px; }
-.emoji-1F1F5-1F1EC { background-position: -3700px 0px; }
-.emoji-1F1F5-1F1ED { background-position: -3720px 0px; }
-.emoji-1F1F5-1F1F0 { background-position: -3740px 0px; }
-.emoji-1F1F5-1F1F1 { background-position: -3760px 0px; }
-.emoji-1F1F5-1F1F7 { background-position: -3780px 0px; }
-.emoji-1F1F5-1F1F8 { background-position: -3800px 0px; }
-.emoji-1F1F5-1F1F9 { background-position: -3820px 0px; }
-.emoji-1F1F5-1F1FC { background-position: -3840px 0px; }
-.emoji-1F1F5-1F1FE { background-position: -3860px 0px; }
-.emoji-1F1F6-1F1E6 { background-position: -3880px 0px; }
-.emoji-1F1F7-1F1F4 { background-position: -3900px 0px; }
-.emoji-1F1F7-1F1F8 { background-position: -3920px 0px; }
-.emoji-1F1F7-1F1FA { background-position: -3940px 0px; }
-.emoji-1F1F7-1F1FC { background-position: -3960px 0px; }
-.emoji-1F1F8-1F1E6 { background-position: -3980px 0px; }
-.emoji-1F1F8-1F1E7 { background-position: -4000px 0px; }
-.emoji-1F1F8-1F1E8 { background-position: -4020px 0px; }
-.emoji-1F1F8-1F1E9 { background-position: -4040px 0px; }
-.emoji-1F1F8-1F1EA { background-position: -4060px 0px; }
-.emoji-1F1F8-1F1EC { background-position: -4080px 0px; }
-.emoji-1F1F8-1F1ED { background-position: -4100px 0px; }
-.emoji-1F1F8-1F1EE { background-position: -4120px 0px; }
-.emoji-1F1F8-1F1F0 { background-position: -4140px 0px; }
-.emoji-1F1F8-1F1F1 { background-position: -4160px 0px; }
-.emoji-1F1F8-1F1F2 { background-position: -4180px 0px; }
-.emoji-1F1F8-1F1F3 { background-position: -4200px 0px; }
-.emoji-1F1F8-1F1F4 { background-position: -4220px 0px; }
-.emoji-1F1F8-1F1F7 { background-position: -4240px 0px; }
-.emoji-1F1F8-1F1F9 { background-position: -4260px 0px; }
-.emoji-1F1F8-1F1FB { background-position: -4280px 0px; }
-.emoji-1F1F8-1F1FE { background-position: -4300px 0px; }
-.emoji-1F1F8-1F1FF { background-position: -4320px 0px; }
-.emoji-1F1F9-1F1E9 { background-position: -4340px 0px; }
-.emoji-1F1F9-1F1EC { background-position: -4360px 0px; }
-.emoji-1F1F9-1F1ED { background-position: -4380px 0px; }
-.emoji-1F1F9-1F1EF { background-position: -4400px 0px; }
-.emoji-1F1F9-1F1F1 { background-position: -4420px 0px; }
-.emoji-1F1F9-1F1F2 { background-position: -4440px 0px; }
-.emoji-1F1F9-1F1F3 { background-position: -4460px 0px; }
-.emoji-1F1F9-1F1F4 { background-position: -4480px 0px; }
-.emoji-1F1F9-1F1F7 { background-position: -4500px 0px; }
-.emoji-1F1F9-1F1F9 { background-position: -4520px 0px; }
-.emoji-1F1F9-1F1FB { background-position: -4540px 0px; }
-.emoji-1F1F9-1F1FC { background-position: -4560px 0px; }
-.emoji-1F1F9-1F1FF { background-position: -4580px 0px; }
-.emoji-1F1FA-1F1E6 { background-position: -4600px 0px; }
-.emoji-1F1FA-1F1EC { background-position: -4620px 0px; }
-.emoji-1F1FA-1F1F8 { background-position: -4640px 0px; }
-.emoji-1F1FA-1F1FE { background-position: -4660px 0px; }
-.emoji-1F1FA-1F1FF { background-position: -4680px 0px; }
-.emoji-1F1FB-1F1E6 { background-position: -4700px 0px; }
-.emoji-1F1FB-1F1E8 { background-position: -4720px 0px; }
-.emoji-1F1FB-1F1EA { background-position: -4740px 0px; }
-.emoji-1F1FB-1F1EE { background-position: -4760px 0px; }
-.emoji-1F1FB-1F1F3 { background-position: -4780px 0px; }
-.emoji-1F1FB-1F1FA { background-position: -4800px 0px; }
-.emoji-1F1FC-1F1EB { background-position: -4820px 0px; }
-.emoji-1F1FC-1F1F8 { background-position: -4840px 0px; }
-.emoji-1F1FD-1F1F0 { background-position: -4860px 0px; }
-.emoji-1F1FE-1F1EA { background-position: -4880px 0px; }
-.emoji-1F1FF-1F1E6 { background-position: -4900px 0px; }
-.emoji-1F1FF-1F1F2 { background-position: -4920px 0px; }
-.emoji-1F1FF-1F1FC { background-position: -4940px 0px; }
-.emoji-1F201 { background-position: -4960px 0px; }
-.emoji-1F202 { background-position: -4980px 0px; }
-.emoji-1F21A { background-position: -5000px 0px; }
-.emoji-1F22F { background-position: -5020px 0px; }
-.emoji-1F232 { background-position: -5040px 0px; }
-.emoji-1F233 { background-position: -5060px 0px; }
-.emoji-1F234 { background-position: -5080px 0px; }
-.emoji-1F235 { background-position: -5100px 0px; }
-.emoji-1F236 { background-position: -5120px 0px; }
-.emoji-1F237 { background-position: -5140px 0px; }
-.emoji-1F238 { background-position: -5160px 0px; }
-.emoji-1F239 { background-position: -5180px 0px; }
-.emoji-1F23A { background-position: -5200px 0px; }
-.emoji-1F250 { background-position: -5220px 0px; }
-.emoji-1F251 { background-position: -5240px 0px; }
-.emoji-1F300 { background-position: -5260px 0px; }
-.emoji-1F301 { background-position: -5280px 0px; }
-.emoji-1F302 { background-position: -5300px 0px; }
-.emoji-1F303 { background-position: -5320px 0px; }
-.emoji-1F304 { background-position: -5340px 0px; }
-.emoji-1F305 { background-position: -5360px 0px; }
-.emoji-1F306 { background-position: -5380px 0px; }
-.emoji-1F307 { background-position: -5400px 0px; }
-.emoji-1F308 { background-position: -5420px 0px; }
-.emoji-1F309 { background-position: -5440px 0px; }
-.emoji-1F30A { background-position: -5460px 0px; }
-.emoji-1F30B { background-position: -5480px 0px; }
-.emoji-1F30C { background-position: -5500px 0px; }
-.emoji-1F30D { background-position: -5520px 0px; }
-.emoji-1F30E { background-position: -5540px 0px; }
-.emoji-1F30F { background-position: -5560px 0px; }
-.emoji-1F310 { background-position: -5580px 0px; }
-.emoji-1F311 { background-position: -5600px 0px; }
-.emoji-1F312 { background-position: -5620px 0px; }
-.emoji-1F313 { background-position: -5640px 0px; }
-.emoji-1F314 { background-position: -5660px 0px; }
-.emoji-1F315 { background-position: -5680px 0px; }
-.emoji-1F316 { background-position: -5700px 0px; }
-.emoji-1F317 { background-position: -5720px 0px; }
-.emoji-1F318 { background-position: -5740px 0px; }
-.emoji-1F319 { background-position: -5760px 0px; }
-.emoji-1F31A { background-position: -5780px 0px; }
-.emoji-1F31B { background-position: -5800px 0px; }
-.emoji-1F31C { background-position: -5820px 0px; }
-.emoji-1F31D { background-position: -5840px 0px; }
-.emoji-1F31E { background-position: -5860px 0px; }
-.emoji-1F31F { background-position: -5880px 0px; }
-.emoji-1F320 { background-position: -5900px 0px; }
-.emoji-1F321 { background-position: -5920px 0px; }
-.emoji-1F327 { background-position: -5940px 0px; }
-.emoji-1F328 { background-position: -5960px 0px; }
-.emoji-1F329 { background-position: -5980px 0px; }
-.emoji-1F32A { background-position: -6000px 0px; }
-.emoji-1F32B { background-position: -6020px 0px; }
-.emoji-1F32C { background-position: -6040px 0px; }
-.emoji-1F330 { background-position: -6060px 0px; }
-.emoji-1F331 { background-position: -6080px 0px; }
-.emoji-1F332 { background-position: -6100px 0px; }
-.emoji-1F333 { background-position: -6120px 0px; }
-.emoji-1F334 { background-position: -6140px 0px; }
-.emoji-1F335 { background-position: -6160px 0px; }
-.emoji-1F336 { background-position: -6180px 0px; }
-.emoji-1F337 { background-position: -6200px 0px; }
-.emoji-1F338 { background-position: -6220px 0px; }
-.emoji-1F339 { background-position: -6240px 0px; }
-.emoji-1F33A { background-position: -6260px 0px; }
-.emoji-1F33B { background-position: -6280px 0px; }
-.emoji-1F33C { background-position: -6300px 0px; }
-.emoji-1F33D { background-position: -6320px 0px; }
-.emoji-1F33E { background-position: -6340px 0px; }
-.emoji-1F33F { background-position: -6360px 0px; }
-.emoji-1F340 { background-position: -6380px 0px; }
-.emoji-1F341 { background-position: -6400px 0px; }
-.emoji-1F342 { background-position: -6420px 0px; }
-.emoji-1F343 { background-position: -6440px 0px; }
-.emoji-1F344 { background-position: -6460px 0px; }
-.emoji-1F345 { background-position: -6480px 0px; }
-.emoji-1F346 { background-position: -6500px 0px; }
-.emoji-1F347 { background-position: -6520px 0px; }
-.emoji-1F348 { background-position: -6540px 0px; }
-.emoji-1F349 { background-position: -6560px 0px; }
-.emoji-1F34A { background-position: -6580px 0px; }
-.emoji-1F34B { background-position: -6600px 0px; }
-.emoji-1F34C { background-position: -6620px 0px; }
-.emoji-1F34D { background-position: -6640px 0px; }
-.emoji-1F34E { background-position: -6660px 0px; }
-.emoji-1F34F { background-position: -6680px 0px; }
-.emoji-1F350 { background-position: -6700px 0px; }
-.emoji-1F351 { background-position: -6720px 0px; }
-.emoji-1F352 { background-position: -6740px 0px; }
-.emoji-1F353 { background-position: -6760px 0px; }
-.emoji-1F354 { background-position: -6780px 0px; }
-.emoji-1F355 { background-position: -6800px 0px; }
-.emoji-1F356 { background-position: -6820px 0px; }
-.emoji-1F357 { background-position: -6840px 0px; }
-.emoji-1F358 { background-position: -6860px 0px; }
-.emoji-1F359 { background-position: -6880px 0px; }
-.emoji-1F35A { background-position: -6900px 0px; }
-.emoji-1F35B { background-position: -6920px 0px; }
-.emoji-1F35C { background-position: -6940px 0px; }
-.emoji-1F35D { background-position: -6960px 0px; }
-.emoji-1F35E { background-position: -6980px 0px; }
-.emoji-1F35F { background-position: -7000px 0px; }
-.emoji-1F360 { background-position: -7020px 0px; }
-.emoji-1F361 { background-position: -7040px 0px; }
-.emoji-1F362 { background-position: -7060px 0px; }
-.emoji-1F363 { background-position: -7080px 0px; }
-.emoji-1F364 { background-position: -7100px 0px; }
-.emoji-1F365 { background-position: -7120px 0px; }
-.emoji-1F366 { background-position: -7140px 0px; }
-.emoji-1F367 { background-position: -7160px 0px; }
-.emoji-1F368 { background-position: -7180px 0px; }
-.emoji-1F369 { background-position: -7200px 0px; }
-.emoji-1F36A { background-position: -7220px 0px; }
-.emoji-1F36B { background-position: -7240px 0px; }
-.emoji-1F36C { background-position: -7260px 0px; }
-.emoji-1F36D { background-position: -7280px 0px; }
-.emoji-1F36E { background-position: -7300px 0px; }
-.emoji-1F36F { background-position: -7320px 0px; }
-.emoji-1F370 { background-position: -7340px 0px; }
-.emoji-1F371 { background-position: -7360px 0px; }
-.emoji-1F372 { background-position: -7380px 0px; }
-.emoji-1F373 { background-position: -7400px 0px; }
-.emoji-1F374 { background-position: -7420px 0px; }
-.emoji-1F375 { background-position: -7440px 0px; }
-.emoji-1F376 { background-position: -7460px 0px; }
-.emoji-1F377 { background-position: -7480px 0px; }
-.emoji-1F378 { background-position: -7500px 0px; }
-.emoji-1F379 { background-position: -7520px 0px; }
-.emoji-1F37A { background-position: -7540px 0px; }
-.emoji-1F37B { background-position: -7560px 0px; }
-.emoji-1F37C { background-position: -7580px 0px; }
-.emoji-1F37D { background-position: -7600px 0px; }
-.emoji-1F380 { background-position: -7620px 0px; }
-.emoji-1F381 { background-position: -7640px 0px; }
-.emoji-1F382 { background-position: -7660px 0px; }
-.emoji-1F383 { background-position: -7680px 0px; }
-.emoji-1F384 { background-position: -7700px 0px; }
-.emoji-1F385 { background-position: -7720px 0px; }
-.emoji-1F386 { background-position: -7740px 0px; }
-.emoji-1F387 { background-position: -7760px 0px; }
-.emoji-1F388 { background-position: -7780px 0px; }
-.emoji-1F389 { background-position: -7800px 0px; }
-.emoji-1F38A { background-position: -7820px 0px; }
-.emoji-1F38B { background-position: -7840px 0px; }
-.emoji-1F38C { background-position: -7860px 0px; }
-.emoji-1F38D { background-position: -7880px 0px; }
-.emoji-1F38E { background-position: -7900px 0px; }
-.emoji-1F38F { background-position: -7920px 0px; }
-.emoji-1F390 { background-position: -7940px 0px; }
-.emoji-1F391 { background-position: -7960px 0px; }
-.emoji-1F392 { background-position: -7980px 0px; }
-.emoji-1F393 { background-position: -8000px 0px; }
-.emoji-1F394 { background-position: -8020px 0px; }
-.emoji-1F395 { background-position: -8040px 0px; }
-.emoji-1F396 { background-position: -8060px 0px; }
-.emoji-1F397 { background-position: -8080px 0px; }
-.emoji-1F398 { background-position: -8100px 0px; }
-.emoji-1F399 { background-position: -8120px 0px; }
-.emoji-1F39A { background-position: -8140px 0px; }
-.emoji-1F39B { background-position: -8160px 0px; }
-.emoji-1F39C { background-position: -8180px 0px; }
-.emoji-1F39D { background-position: -8200px 0px; }
-.emoji-1F39E { background-position: -8220px 0px; }
-.emoji-1F39F { background-position: -8240px 0px; }
-.emoji-1F3A0 { background-position: -8260px 0px; }
-.emoji-1F3A1 { background-position: -8280px 0px; }
-.emoji-1F3A2 { background-position: -8300px 0px; }
-.emoji-1F3A3 { background-position: -8320px 0px; }
-.emoji-1F3A4 { background-position: -8340px 0px; }
-.emoji-1F3A5 { background-position: -8360px 0px; }
-.emoji-1F3A6 { background-position: -8380px 0px; }
-.emoji-1F3A7 { background-position: -8400px 0px; }
-.emoji-1F3A8 { background-position: -8420px 0px; }
-.emoji-1F3A9 { background-position: -8440px 0px; }
-.emoji-1F3AA { background-position: -8460px 0px; }
-.emoji-1F3AB { background-position: -8480px 0px; }
-.emoji-1F3AC { background-position: -8500px 0px; }
-.emoji-1F3AD { background-position: -8520px 0px; }
-.emoji-1F3AE { background-position: -8540px 0px; }
-.emoji-1F3AF { background-position: -8560px 0px; }
-.emoji-1F3B0 { background-position: -8580px 0px; }
-.emoji-1F3B1 { background-position: -8600px 0px; }
-.emoji-1F3B2 { background-position: -8620px 0px; }
-.emoji-1F3B3 { background-position: -8640px 0px; }
-.emoji-1F3B4 { background-position: -8660px 0px; }
-.emoji-1F3B5 { background-position: -8680px 0px; }
-.emoji-1F3B6 { background-position: -8700px 0px; }
-.emoji-1F3B7 { background-position: -8720px 0px; }
-.emoji-1F3B8 { background-position: -8740px 0px; }
-.emoji-1F3B9 { background-position: -8760px 0px; }
-.emoji-1F3BA { background-position: -8780px 0px; }
-.emoji-1F3BB { background-position: -8800px 0px; }
-.emoji-1F3BC { background-position: -8820px 0px; }
-.emoji-1F3BD { background-position: -8840px 0px; }
-.emoji-1F3BE { background-position: -8860px 0px; }
-.emoji-1F3BF { background-position: -8880px 0px; }
-.emoji-1F3C0 { background-position: -8900px 0px; }
-.emoji-1F3C1 { background-position: -8920px 0px; }
-.emoji-1F3C2 { background-position: -8940px 0px; }
-.emoji-1F3C3 { background-position: -8960px 0px; }
-.emoji-1F3C4 { background-position: -8980px 0px; }
-.emoji-1F3C5 { background-position: -9000px 0px; }
-.emoji-1F3C6 { background-position: -9020px 0px; }
-.emoji-1F3C7 { background-position: -9040px 0px; }
-.emoji-1F3C8 { background-position: -9060px 0px; }
-.emoji-1F3C9 { background-position: -9080px 0px; }
-.emoji-1F3CA { background-position: -9100px 0px; }
-.emoji-1F3CB { background-position: -9120px 0px; }
-.emoji-1F3CC { background-position: -9140px 0px; }
-.emoji-1F3CD { background-position: -9160px 0px; }
-.emoji-1F3CE { background-position: -9180px 0px; }
-.emoji-1F3D4 { background-position: -9200px 0px; }
-.emoji-1F3D5 { background-position: -9220px 0px; }
-.emoji-1F3D6 { background-position: -9240px 0px; }
-.emoji-1F3D7 { background-position: -9260px 0px; }
-.emoji-1F3D8 { background-position: -9280px 0px; }
-.emoji-1F3D9 { background-position: -9300px 0px; }
-.emoji-1F3DA { background-position: -9320px 0px; }
-.emoji-1F3DB { background-position: -9340px 0px; }
-.emoji-1F3DC { background-position: -9360px 0px; }
-.emoji-1F3DD { background-position: -9380px 0px; }
-.emoji-1F3DE { background-position: -9400px 0px; }
-.emoji-1F3DF { background-position: -9420px 0px; }
-.emoji-1F3E0 { background-position: -9440px 0px; }
-.emoji-1F3E1 { background-position: -9460px 0px; }
-.emoji-1F3E2 { background-position: -9480px 0px; }
-.emoji-1F3E3 { background-position: -9500px 0px; }
-.emoji-1F3E4 { background-position: -9520px 0px; }
-.emoji-1F3E5 { background-position: -9540px 0px; }
-.emoji-1F3E6 { background-position: -9560px 0px; }
-.emoji-1F3E7 { background-position: -9580px 0px; }
-.emoji-1F3E8 { background-position: -9600px 0px; }
-.emoji-1F3E9 { background-position: -9620px 0px; }
-.emoji-1F3EA { background-position: -9640px 0px; }
-.emoji-1F3EB { background-position: -9660px 0px; }
-.emoji-1F3EC { background-position: -9680px 0px; }
-.emoji-1F3ED { background-position: -9700px 0px; }
-.emoji-1F3EE { background-position: -9720px 0px; }
-.emoji-1F3EF { background-position: -9740px 0px; }
-.emoji-1F3F0 { background-position: -9760px 0px; }
-.emoji-1F3F1 { background-position: -9780px 0px; }
-.emoji-1F3F2 { background-position: -9800px 0px; }
-.emoji-1F3F3 { background-position: -9820px 0px; }
-.emoji-1F3F4 { background-position: -9840px 0px; }
-.emoji-1F3F5 { background-position: -9860px 0px; }
-.emoji-1F3F6 { background-position: -9880px 0px; }
-.emoji-1F3F7 { background-position: -9900px 0px; }
-.emoji-1F400 { background-position: -9920px 0px; }
-.emoji-1F401 { background-position: -9940px 0px; }
-.emoji-1F402 { background-position: -9960px 0px; }
-.emoji-1F403 { background-position: -9980px 0px; }
-.emoji-1F404 { background-position: -10000px 0px; }
-.emoji-1F405 { background-position: -10020px 0px; }
-.emoji-1F406 { background-position: -10040px 0px; }
-.emoji-1F407 { background-position: -10060px 0px; }
-.emoji-1F408 { background-position: -10080px 0px; }
-.emoji-1F409 { background-position: -10100px 0px; }
-.emoji-1F40A { background-position: -10120px 0px; }
-.emoji-1F40B { background-position: -10140px 0px; }
-.emoji-1F40C { background-position: -10160px 0px; }
-.emoji-1F40D { background-position: -10180px 0px; }
-.emoji-1F40E { background-position: -10200px 0px; }
-.emoji-1F40F { background-position: -10220px 0px; }
-.emoji-1F410 { background-position: -10240px 0px; }
-.emoji-1F411 { background-position: -10260px 0px; }
-.emoji-1F412 { background-position: -10280px 0px; }
-.emoji-1F413 { background-position: -10300px 0px; }
-.emoji-1F414 { background-position: -10320px 0px; }
-.emoji-1F415 { background-position: -10340px 0px; }
-.emoji-1F416 { background-position: -10360px 0px; }
-.emoji-1F417 { background-position: -10380px 0px; }
-.emoji-1F418 { background-position: -10400px 0px; }
-.emoji-1F419 { background-position: -10420px 0px; }
-.emoji-1F41A { background-position: -10440px 0px; }
-.emoji-1F41B { background-position: -10460px 0px; }
-.emoji-1F41C { background-position: -10480px 0px; }
-.emoji-1F41D { background-position: -10500px 0px; }
-.emoji-1F41E { background-position: -10520px 0px; }
-.emoji-1F41F { background-position: -10540px 0px; }
-.emoji-1F420 { background-position: -10560px 0px; }
-.emoji-1F421 { background-position: -10580px 0px; }
-.emoji-1F422 { background-position: -10600px 0px; }
-.emoji-1F423 { background-position: -10620px 0px; }
-.emoji-1F424 { background-position: -10640px 0px; }
-.emoji-1F425 { background-position: -10660px 0px; }
-.emoji-1F426 { background-position: -10680px 0px; }
-.emoji-1F427 { background-position: -10700px 0px; }
-.emoji-1F428 { background-position: -10720px 0px; }
-.emoji-1F429 { background-position: -10740px 0px; }
-.emoji-1F42A { background-position: -10760px 0px; }
-.emoji-1F42B { background-position: -10780px 0px; }
-.emoji-1F42C { background-position: -10800px 0px; }
-.emoji-1F42D { background-position: -10820px 0px; }
-.emoji-1F42E { background-position: -10840px 0px; }
-.emoji-1F42F { background-position: -10860px 0px; }
-.emoji-1F430 { background-position: -10880px 0px; }
-.emoji-1F431 { background-position: -10900px 0px; }
-.emoji-1F432 { background-position: -10920px 0px; }
-.emoji-1F433 { background-position: -10940px 0px; }
-.emoji-1F434 { background-position: -10960px 0px; }
-.emoji-1F435 { background-position: -10980px 0px; }
-.emoji-1F436 { background-position: -11000px 0px; }
-.emoji-1F437 { background-position: -11020px 0px; }
-.emoji-1F438 { background-position: -11040px 0px; }
-.emoji-1F439 { background-position: -11060px 0px; }
-.emoji-1F43A { background-position: -11080px 0px; }
-.emoji-1F43B { background-position: -11100px 0px; }
-.emoji-1F43C { background-position: -11120px 0px; }
-.emoji-1F43D { background-position: -11140px 0px; }
-.emoji-1F43E { background-position: -11160px 0px; }
-.emoji-1F43F { background-position: -11180px 0px; }
-.emoji-1F440 { background-position: -11200px 0px; }
-.emoji-1F441 { background-position: -11220px 0px; }
-.emoji-1F442 { background-position: -11240px 0px; }
-.emoji-1F443 { background-position: -11260px 0px; }
-.emoji-1F444 { background-position: -11280px 0px; }
-.emoji-1F445 { background-position: -11300px 0px; }
-.emoji-1F446 { background-position: -11320px 0px; }
-.emoji-1F447 { background-position: -11340px 0px; }
-.emoji-1F448 { background-position: -11360px 0px; }
-.emoji-1F449 { background-position: -11380px 0px; }
-.emoji-1F44A { background-position: -11400px 0px; }
-.emoji-1F44B { background-position: -11420px 0px; }
-.emoji-1F44C { background-position: -11440px 0px; }
-.emoji-1F44D { background-position: -11460px 0px; }
-.emoji-1F44E { background-position: -11480px 0px; }
-.emoji-1F44F { background-position: -11500px 0px; }
-.emoji-1F450 { background-position: -11520px 0px; }
-.emoji-1F451 { background-position: -11540px 0px; }
-.emoji-1F452 { background-position: -11560px 0px; }
-.emoji-1F453 { background-position: -11580px 0px; }
-.emoji-1F454 { background-position: -11600px 0px; }
-.emoji-1F455 { background-position: -11620px 0px; }
-.emoji-1F456 { background-position: -11640px 0px; }
-.emoji-1F457 { background-position: -11660px 0px; }
-.emoji-1F458 { background-position: -11680px 0px; }
-.emoji-1F459 { background-position: -11700px 0px; }
-.emoji-1F45A { background-position: -11720px 0px; }
-.emoji-1F45B { background-position: -11740px 0px; }
-.emoji-1F45C { background-position: -11760px 0px; }
-.emoji-1F45D { background-position: -11780px 0px; }
-.emoji-1F45E { background-position: -11800px 0px; }
-.emoji-1F45F { background-position: -11820px 0px; }
-.emoji-1F460 { background-position: -11840px 0px; }
-.emoji-1F461 { background-position: -11860px 0px; }
-.emoji-1F462 { background-position: -11880px 0px; }
-.emoji-1F463 { background-position: -11900px 0px; }
-.emoji-1F464 { background-position: -11920px 0px; }
-.emoji-1F465 { background-position: -11940px 0px; }
-.emoji-1F466 { background-position: -11960px 0px; }
-.emoji-1F467 { background-position: -11980px 0px; }
-.emoji-1F468 { background-position: -12000px 0px; }
-.emoji-1F468-1F468-1F466 { background-position: -12020px 0px; }
-.emoji-1F468-1F468-1F466-1F466 { background-position: -12040px 0px; }
-.emoji-1F468-1F468-1F467 { background-position: -12060px 0px; }
-.emoji-1F468-1F468-1F467-1F466 { background-position: -12080px 0px; }
-.emoji-1F468-1F468-1F467-1F467 { background-position: -12100px 0px; }
-.emoji-1F468-1F469-1F466-1F466 { background-position: -12120px 0px; }
-.emoji-1F468-1F469-1F467 { background-position: -12140px 0px; }
-.emoji-1F468-1F469-1F467-1F466 { background-position: -12160px 0px; }
-.emoji-1F468-1F469-1F467-1F467 { background-position: -12180px 0px; }
-.emoji-1F468-2764-1F468 { background-position: -12200px 0px; }
-.emoji-1F468-2764-1F48B-1F468 { background-position: -12220px 0px; }
-.emoji-1F469 { background-position: -12240px 0px; }
-.emoji-1F469-1F469-1F466 { background-position: -12260px 0px; }
-.emoji-1F469-1F469-1F466-1F466 { background-position: -12280px 0px; }
-.emoji-1F469-1F469-1F467 { background-position: -12300px 0px; }
-.emoji-1F469-1F469-1F467-1F466 { background-position: -12320px 0px; }
-.emoji-1F469-1F469-1F467-1F467 { background-position: -12340px 0px; }
-.emoji-1F469-2764-1F469 { background-position: -12360px 0px; }
-.emoji-1F469-2764-1F48B-1F469 { background-position: -12380px 0px; }
-.emoji-1F46A { background-position: -12400px 0px; }
-.emoji-1F46B { background-position: -12420px 0px; }
-.emoji-1F46C { background-position: -12440px 0px; }
-.emoji-1F46D { background-position: -12460px 0px; }
-.emoji-1F46E { background-position: -12480px 0px; }
-.emoji-1F46F { background-position: -12500px 0px; }
-.emoji-1F470 { background-position: -12520px 0px; }
-.emoji-1F471 { background-position: -12540px 0px; }
-.emoji-1F472 { background-position: -12560px 0px; }
-.emoji-1F473 { background-position: -12580px 0px; }
-.emoji-1F474 { background-position: -12600px 0px; }
-.emoji-1F475 { background-position: -12620px 0px; }
-.emoji-1F476 { background-position: -12640px 0px; }
-.emoji-1F477 { background-position: -12660px 0px; }
-.emoji-1F478 { background-position: -12680px 0px; }
-.emoji-1F479 { background-position: -12700px 0px; }
-.emoji-1F47A { background-position: -12720px 0px; }
-.emoji-1F47B { background-position: -12740px 0px; }
-.emoji-1F47C { background-position: -12760px 0px; }
-.emoji-1F47D { background-position: -12780px 0px; }
-.emoji-1F47E { background-position: -12800px 0px; }
-.emoji-1F47F { background-position: -12820px 0px; }
-.emoji-1F480 { background-position: -12840px 0px; }
-.emoji-1F481 { background-position: -12860px 0px; }
-.emoji-1F482 { background-position: -12880px 0px; }
-.emoji-1F483 { background-position: -12900px 0px; }
-.emoji-1F484 { background-position: -12920px 0px; }
-.emoji-1F485 { background-position: -12940px 0px; }
-.emoji-1F486 { background-position: -12960px 0px; }
-.emoji-1F487 { background-position: -12980px 0px; }
-.emoji-1F488 { background-position: -13000px 0px; }
-.emoji-1F489 { background-position: -13020px 0px; }
-.emoji-1F48A { background-position: -13040px 0px; }
-.emoji-1F48B { background-position: -13060px 0px; }
-.emoji-1F48C { background-position: -13080px 0px; }
-.emoji-1F48D { background-position: -13100px 0px; }
-.emoji-1F48E { background-position: -13120px 0px; }
-.emoji-1F48F { background-position: -13140px 0px; }
-.emoji-1F490 { background-position: -13160px 0px; }
-.emoji-1F491 { background-position: -13180px 0px; }
-.emoji-1F492 { background-position: -13200px 0px; }
-.emoji-1F493 { background-position: -13220px 0px; }
-.emoji-1F494 { background-position: -13240px 0px; }
-.emoji-1F495 { background-position: -13260px 0px; }
-.emoji-1F496 { background-position: -13280px 0px; }
-.emoji-1F497 { background-position: -13300px 0px; }
-.emoji-1F498 { background-position: -13320px 0px; }
-.emoji-1F499 { background-position: -13340px 0px; }
-.emoji-1F49A { background-position: -13360px 0px; }
-.emoji-1F49B { background-position: -13380px 0px; }
-.emoji-1F49C { background-position: -13400px 0px; }
-.emoji-1F49D { background-position: -13420px 0px; }
-.emoji-1F49E { background-position: -13440px 0px; }
-.emoji-1F49F { background-position: -13460px 0px; }
-.emoji-1F4A0 { background-position: -13480px 0px; }
-.emoji-1F4A1 { background-position: -13500px 0px; }
-.emoji-1F4A2 { background-position: -13520px 0px; }
-.emoji-1F4A3 { background-position: -13540px 0px; }
-.emoji-1F4A4 { background-position: -13560px 0px; }
-.emoji-1F4A5 { background-position: -13580px 0px; }
-.emoji-1F4A6 { background-position: -13600px 0px; }
-.emoji-1F4A7 { background-position: -13620px 0px; }
-.emoji-1F4A8 { background-position: -13640px 0px; }
-.emoji-1F4A9 { background-position: -13660px 0px; }
-.emoji-1F4AA { background-position: -13680px 0px; }
-.emoji-1F4AB { background-position: -13700px 0px; }
-.emoji-1F4AC { background-position: -13720px 0px; }
-.emoji-1F4AD { background-position: -13740px 0px; }
-.emoji-1F4AE { background-position: -13760px 0px; }
-.emoji-1F4AF { background-position: -13780px 0px; }
-.emoji-1F4B0 { background-position: -13800px 0px; }
-.emoji-1F4B1 { background-position: -13820px 0px; }
-.emoji-1F4B2 { background-position: -13840px 0px; }
-.emoji-1F4B3 { background-position: -13860px 0px; }
-.emoji-1F4B4 { background-position: -13880px 0px; }
-.emoji-1F4B5 { background-position: -13900px 0px; }
-.emoji-1F4B6 { background-position: -13920px 0px; }
-.emoji-1F4B7 { background-position: -13940px 0px; }
-.emoji-1F4B8 { background-position: -13960px 0px; }
-.emoji-1F4B9 { background-position: -13980px 0px; }
-.emoji-1F4BA { background-position: -14000px 0px; }
-.emoji-1F4BB { background-position: -14020px 0px; }
-.emoji-1F4BC { background-position: -14040px 0px; }
-.emoji-1F4BD { background-position: -14060px 0px; }
-.emoji-1F4BE { background-position: -14080px 0px; }
-.emoji-1F4BF { background-position: -14100px 0px; }
-.emoji-1F4C0 { background-position: -14120px 0px; }
-.emoji-1F4C1 { background-position: -14140px 0px; }
-.emoji-1F4C2 { background-position: -14160px 0px; }
-.emoji-1F4C3 { background-position: -14180px 0px; }
-.emoji-1F4C4 { background-position: -14200px 0px; }
-.emoji-1F4C5 { background-position: -14220px 0px; }
-.emoji-1F4C6 { background-position: -14240px 0px; }
-.emoji-1F4C7 { background-position: -14260px 0px; }
-.emoji-1F4C8 { background-position: -14280px 0px; }
-.emoji-1F4C9 { background-position: -14300px 0px; }
-.emoji-1F4CA { background-position: -14320px 0px; }
-.emoji-1F4CB { background-position: -14340px 0px; }
-.emoji-1F4CC { background-position: -14360px 0px; }
-.emoji-1F4CD { background-position: -14380px 0px; }
-.emoji-1F4CE { background-position: -14400px 0px; }
-.emoji-1F4CF { background-position: -14420px 0px; }
-.emoji-1F4D0 { background-position: -14440px 0px; }
-.emoji-1F4D1 { background-position: -14460px 0px; }
-.emoji-1F4D2 { background-position: -14480px 0px; }
-.emoji-1F4D3 { background-position: -14500px 0px; }
-.emoji-1F4D4 { background-position: -14520px 0px; }
-.emoji-1F4D5 { background-position: -14540px 0px; }
-.emoji-1F4D6 { background-position: -14560px 0px; }
-.emoji-1F4D7 { background-position: -14580px 0px; }
-.emoji-1F4D8 { background-position: -14600px 0px; }
-.emoji-1F4D9 { background-position: -14620px 0px; }
-.emoji-1F4DA { background-position: -14640px 0px; }
-.emoji-1F4DB { background-position: -14660px 0px; }
-.emoji-1F4DC { background-position: -14680px 0px; }
-.emoji-1F4DD { background-position: -14700px 0px; }
-.emoji-1F4DE { background-position: -14720px 0px; }
-.emoji-1F4DF { background-position: -14740px 0px; }
-.emoji-1F4E0 { background-position: -14760px 0px; }
-.emoji-1F4E1 { background-position: -14780px 0px; }
-.emoji-1F4E2 { background-position: -14800px 0px; }
-.emoji-1F4E3 { background-position: -14820px 0px; }
-.emoji-1F4E4 { background-position: -14840px 0px; }
-.emoji-1F4E5 { background-position: -14860px 0px; }
-.emoji-1F4E6 { background-position: -14880px 0px; }
-.emoji-1F4E7 { background-position: -14900px 0px; }
-.emoji-1F4E8 { background-position: -14920px 0px; }
-.emoji-1F4E9 { background-position: -14940px 0px; }
-.emoji-1F4EA { background-position: -14960px 0px; }
-.emoji-1F4EB { background-position: -14980px 0px; }
-.emoji-1F4EC { background-position: -15000px 0px; }
-.emoji-1F4ED { background-position: -15020px 0px; }
-.emoji-1F4EE { background-position: -15040px 0px; }
-.emoji-1F4EF { background-position: -15060px 0px; }
-.emoji-1F4F0 { background-position: -15080px 0px; }
-.emoji-1F4F1 { background-position: -15100px 0px; }
-.emoji-1F4F2 { background-position: -15120px 0px; }
-.emoji-1F4F3 { background-position: -15140px 0px; }
-.emoji-1F4F4 { background-position: -15160px 0px; }
-.emoji-1F4F5 { background-position: -15180px 0px; }
-.emoji-1F4F6 { background-position: -15200px 0px; }
-.emoji-1F4F7 { background-position: -15220px 0px; }
-.emoji-1F4F8 { background-position: -15240px 0px; }
-.emoji-1F4F9 { background-position: -15260px 0px; }
-.emoji-1F4FA { background-position: -15280px 0px; }
-.emoji-1F4FB { background-position: -15300px 0px; }
-.emoji-1F4FC { background-position: -15320px 0px; }
-.emoji-1F4FD { background-position: -15340px 0px; }
-.emoji-1F4FE { background-position: -15360px 0px; }
-.emoji-1F500 { background-position: -15380px 0px; }
-.emoji-1F501 { background-position: -15400px 0px; }
-.emoji-1F502 { background-position: -15420px 0px; }
-.emoji-1F503 { background-position: -15440px 0px; }
-.emoji-1F504 { background-position: -15460px 0px; }
-.emoji-1F505 { background-position: -15480px 0px; }
-.emoji-1F506 { background-position: -15500px 0px; }
-.emoji-1F507 { background-position: -15520px 0px; }
-.emoji-1F508 { background-position: -15540px 0px; }
-.emoji-1F509 { background-position: -15560px 0px; }
-.emoji-1F50A { background-position: -15580px 0px; }
-.emoji-1F50B { background-position: -15600px 0px; }
-.emoji-1F50C { background-position: -15620px 0px; }
-.emoji-1F50D { background-position: -15640px 0px; }
-.emoji-1F50E { background-position: -15660px 0px; }
-.emoji-1F50F { background-position: -15680px 0px; }
-.emoji-1F510 { background-position: -15700px 0px; }
-.emoji-1F511 { background-position: -15720px 0px; }
-.emoji-1F512 { background-position: -15740px 0px; }
-.emoji-1F513 { background-position: -15760px 0px; }
-.emoji-1F514 { background-position: -15780px 0px; }
-.emoji-1F515 { background-position: -15800px 0px; }
-.emoji-1F516 { background-position: -15820px 0px; }
-.emoji-1F517 { background-position: -15840px 0px; }
-.emoji-1F518 { background-position: -15860px 0px; }
-.emoji-1F519 { background-position: -15880px 0px; }
-.emoji-1F51A { background-position: -15900px 0px; }
-.emoji-1F51B { background-position: -15920px 0px; }
-.emoji-1F51C { background-position: -15940px 0px; }
-.emoji-1F51D { background-position: -15960px 0px; }
-.emoji-1F51E { background-position: -15980px 0px; }
-.emoji-1F51F { background-position: -16000px 0px; }
-.emoji-1F520 { background-position: -16020px 0px; }
-.emoji-1F521 { background-position: -16040px 0px; }
-.emoji-1F522 { background-position: -16060px 0px; }
-.emoji-1F523 { background-position: -16080px 0px; }
-.emoji-1F524 { background-position: -16100px 0px; }
-.emoji-1F525 { background-position: -16120px 0px; }
-.emoji-1F526 { background-position: -16140px 0px; }
-.emoji-1F527 { background-position: -16160px 0px; }
-.emoji-1F528 { background-position: -16180px 0px; }
-.emoji-1F529 { background-position: -16200px 0px; }
-.emoji-1F52A { background-position: -16220px 0px; }
-.emoji-1F52B { background-position: -16240px 0px; }
-.emoji-1F52C { background-position: -16260px 0px; }
-.emoji-1F52D { background-position: -16280px 0px; }
-.emoji-1F52E { background-position: -16300px 0px; }
-.emoji-1F52F { background-position: -16320px 0px; }
-.emoji-1F530 { background-position: -16340px 0px; }
-.emoji-1F531 { background-position: -16360px 0px; }
-.emoji-1F532 { background-position: -16380px 0px; }
-.emoji-1F533 { background-position: -16400px 0px; }
-.emoji-1F534 { background-position: -16420px 0px; }
-.emoji-1F535 { background-position: -16440px 0px; }
-.emoji-1F536 { background-position: -16460px 0px; }
-.emoji-1F537 { background-position: -16480px 0px; }
-.emoji-1F538 { background-position: -16500px 0px; }
-.emoji-1F539 { background-position: -16520px 0px; }
-.emoji-1F53A { background-position: -16540px 0px; }
-.emoji-1F53B { background-position: -16560px 0px; }
-.emoji-1F53C { background-position: -16580px 0px; }
-.emoji-1F53D { background-position: -16600px 0px; }
-.emoji-1F546 { background-position: -16620px 0px; }
-.emoji-1F547 { background-position: -16640px 0px; }
-.emoji-1F548 { background-position: -16660px 0px; }
-.emoji-1F549 { background-position: -16680px 0px; }
-.emoji-1F54A { background-position: -16700px 0px; }
-.emoji-1F550 { background-position: -16720px 0px; }
-.emoji-1F551 { background-position: -16740px 0px; }
-.emoji-1F552 { background-position: -16760px 0px; }
-.emoji-1F553 { background-position: -16780px 0px; }
-.emoji-1F554 { background-position: -16800px 0px; }
-.emoji-1F555 { background-position: -16820px 0px; }
-.emoji-1F556 { background-position: -16840px 0px; }
-.emoji-1F557 { background-position: -16860px 0px; }
-.emoji-1F558 { background-position: -16880px 0px; }
-.emoji-1F559 { background-position: -16900px 0px; }
-.emoji-1F55A { background-position: -16920px 0px; }
-.emoji-1F55B { background-position: -16940px 0px; }
-.emoji-1F55C { background-position: -16960px 0px; }
-.emoji-1F55D { background-position: -16980px 0px; }
-.emoji-1F55E { background-position: -17000px 0px; }
-.emoji-1F55F { background-position: -17020px 0px; }
-.emoji-1F560 { background-position: -17040px 0px; }
-.emoji-1F561 { background-position: -17060px 0px; }
-.emoji-1F562 { background-position: -17080px 0px; }
-.emoji-1F563 { background-position: -17100px 0px; }
-.emoji-1F564 { background-position: -17120px 0px; }
-.emoji-1F565 { background-position: -17140px 0px; }
-.emoji-1F566 { background-position: -17160px 0px; }
-.emoji-1F567 { background-position: -17180px 0px; }
-.emoji-1F568 { background-position: -17200px 0px; }
-.emoji-1F569 { background-position: -17220px 0px; }
-.emoji-1F56A { background-position: -17240px 0px; }
-.emoji-1F56B { background-position: -17260px 0px; }
-.emoji-1F56C { background-position: -17280px 0px; }
-.emoji-1F56D { background-position: -17300px 0px; }
-.emoji-1F56E { background-position: -17320px 0px; }
-.emoji-1F56F { background-position: -17340px 0px; }
-.emoji-1F570 { background-position: -17360px 0px; }
-.emoji-1F571 { background-position: -17380px 0px; }
-.emoji-1F572 { background-position: -17400px 0px; }
-.emoji-1F573 { background-position: -17420px 0px; }
-.emoji-1F574 { background-position: -17440px 0px; }
-.emoji-1F575 { background-position: -17460px 0px; }
-.emoji-1F576 { background-position: -17480px 0px; }
-.emoji-1F577 { background-position: -17500px 0px; }
-.emoji-1F578 { background-position: -17520px 0px; }
-.emoji-1F579 { background-position: -17540px 0px; }
-.emoji-1F57B { background-position: -17560px 0px; }
-.emoji-1F57E { background-position: -17580px 0px; }
-.emoji-1F57F { background-position: -17600px 0px; }
-.emoji-1F581 { background-position: -17620px 0px; }
-.emoji-1F582 { background-position: -17640px 0px; }
-.emoji-1F583 { background-position: -17660px 0px; }
-.emoji-1F585 { background-position: -17680px 0px; }
-.emoji-1F586 { background-position: -17700px 0px; }
-.emoji-1F587 { background-position: -17720px 0px; }
-.emoji-1F588 { background-position: -17740px 0px; }
-.emoji-1F589 { background-position: -17760px 0px; }
-.emoji-1F58A { background-position: -17780px 0px; }
-.emoji-1F58B { background-position: -17800px 0px; }
-.emoji-1F58C { background-position: -17820px 0px; }
-.emoji-1F58D { background-position: -17840px 0px; }
-.emoji-1F58E { background-position: -17860px 0px; }
-.emoji-1F58F { background-position: -17880px 0px; }
-.emoji-1F590 { background-position: -17900px 0px; }
-.emoji-1F591 { background-position: -17920px 0px; }
-.emoji-1F592 { background-position: -17940px 0px; }
-.emoji-1F593 { background-position: -17960px 0px; }
-.emoji-1F594 { background-position: -17980px 0px; }
-.emoji-1F595 { background-position: -18000px 0px; }
-.emoji-1F596 { background-position: -18020px 0px; }
-.emoji-1F597 { background-position: -18040px 0px; }
-.emoji-1F598 { background-position: -18060px 0px; }
-.emoji-1F599 { background-position: -18080px 0px; }
-.emoji-1F59E { background-position: -18100px 0px; }
-.emoji-1F59F { background-position: -18120px 0px; }
-.emoji-1F5A5 { background-position: -18140px 0px; }
-.emoji-1F5A6 { background-position: -18160px 0px; }
-.emoji-1F5A7 { background-position: -18180px 0px; }
-.emoji-1F5A8 { background-position: -18200px 0px; }
-.emoji-1F5A9 { background-position: -18220px 0px; }
-.emoji-1F5AA { background-position: -18240px 0px; }
-.emoji-1F5AB { background-position: -18260px 0px; }
-.emoji-1F5AD { background-position: -18280px 0px; }
-.emoji-1F5AE { background-position: -18300px 0px; }
-.emoji-1F5AF { background-position: -18320px 0px; }
-.emoji-1F5B2 { background-position: -18340px 0px; }
-.emoji-1F5B3 { background-position: -18360px 0px; }
-.emoji-1F5B4 { background-position: -18380px 0px; }
-.emoji-1F5B8 { background-position: -18400px 0px; }
-.emoji-1F5B9 { background-position: -18420px 0px; }
-.emoji-1F5BC { background-position: -18440px 0px; }
-.emoji-1F5BD { background-position: -18460px 0px; }
-.emoji-1F5BE { background-position: -18480px 0px; }
-.emoji-1F5C0 { background-position: -18500px 0px; }
-.emoji-1F5C1 { background-position: -18520px 0px; }
-.emoji-1F5C2 { background-position: -18540px 0px; }
-.emoji-1F5C3 { background-position: -18560px 0px; }
-.emoji-1F5C4 { background-position: -18580px 0px; }
-.emoji-1F5C6 { background-position: -18600px 0px; }
-.emoji-1F5C7 { background-position: -18620px 0px; }
-.emoji-1F5C9 { background-position: -18640px 0px; }
-.emoji-1F5CA { background-position: -18660px 0px; }
-.emoji-1F5CE { background-position: -18680px 0px; }
-.emoji-1F5CF { background-position: -18700px 0px; }
-.emoji-1F5D0 { background-position: -18720px 0px; }
-.emoji-1F5D1 { background-position: -18740px 0px; }
-.emoji-1F5D2 { background-position: -18760px 0px; }
-.emoji-1F5D3 { background-position: -18780px 0px; }
-.emoji-1F5D4 { background-position: -18800px 0px; }
-.emoji-1F5D8 { background-position: -18820px 0px; }
-.emoji-1F5D9 { background-position: -18840px 0px; }
-.emoji-1F5DC { background-position: -18860px 0px; }
-.emoji-1F5DD { background-position: -18880px 0px; }
-.emoji-1F5DE { background-position: -18900px 0px; }
-.emoji-1F5E0 { background-position: -18920px 0px; }
-.emoji-1F5E1 { background-position: -18940px 0px; }
-.emoji-1F5E2 { background-position: -18960px 0px; }
-.emoji-1F5E3 { background-position: -18980px 0px; }
-.emoji-1F5E8 { background-position: -19000px 0px; }
-.emoji-1F5E9 { background-position: -19020px 0px; }
-.emoji-1F5EA { background-position: -19040px 0px; }
-.emoji-1F5EB { background-position: -19060px 0px; }
-.emoji-1F5EC { background-position: -19080px 0px; }
-.emoji-1F5ED { background-position: -19100px 0px; }
-.emoji-1F5EE { background-position: -19120px 0px; }
-.emoji-1F5EF { background-position: -19140px 0px; }
-.emoji-1F5F0 { background-position: -19160px 0px; }
-.emoji-1F5F1 { background-position: -19180px 0px; }
-.emoji-1F5F2 { background-position: -19200px 0px; }
-.emoji-1F5F3 { background-position: -19220px 0px; }
-.emoji-1F5F4 { background-position: -19240px 0px; }
-.emoji-1F5F5 { background-position: -19260px 0px; }
-.emoji-1F5F8 { background-position: -19280px 0px; }
-.emoji-1F5F9 { background-position: -19300px 0px; }
-.emoji-1F5FA { background-position: -19320px 0px; }
-.emoji-1F5FB { background-position: -19340px 0px; }
-.emoji-1F5FC { background-position: -19360px 0px; }
-.emoji-1F5FD { background-position: -19380px 0px; }
-.emoji-1F5FE { background-position: -19400px 0px; }
-.emoji-1F5FF { background-position: -19420px 0px; }
-.emoji-1F600 { background-position: -19440px 0px; }
-.emoji-1F601 { background-position: -19460px 0px; }
-.emoji-1F602 { background-position: -19480px 0px; }
-.emoji-1F603 { background-position: -19500px 0px; }
-.emoji-1F604 { background-position: -19520px 0px; }
-.emoji-1F605 { background-position: -19540px 0px; }
-.emoji-1F606 { background-position: -19560px 0px; }
-.emoji-1F607 { background-position: -19580px 0px; }
-.emoji-1F608 { background-position: -19600px 0px; }
-.emoji-1F609 { background-position: -19620px 0px; }
-.emoji-1F60A { background-position: -19640px 0px; }
-.emoji-1F60B { background-position: -19660px 0px; }
-.emoji-1F60C { background-position: -19680px 0px; }
-.emoji-1F60D { background-position: -19700px 0px; }
-.emoji-1F60E { background-position: -19720px 0px; }
-.emoji-1F60F { background-position: -19740px 0px; }
-.emoji-1F610 { background-position: -19760px 0px; }
-.emoji-1F611 { background-position: -19780px 0px; }
-.emoji-1F612 { background-position: -19800px 0px; }
-.emoji-1F613 { background-position: -19820px 0px; }
-.emoji-1F614 { background-position: -19840px 0px; }
-.emoji-1F615 { background-position: -19860px 0px; }
-.emoji-1F616 { background-position: -19880px 0px; }
-.emoji-1F617 { background-position: -19900px 0px; }
-.emoji-1F618 { background-position: -19920px 0px; }
-.emoji-1F619 { background-position: -19940px 0px; }
-.emoji-1F61A { background-position: -19960px 0px; }
-.emoji-1F61B { background-position: -19980px 0px; }
-.emoji-1F61C { background-position: -20000px 0px; }
-.emoji-1F61D { background-position: -20020px 0px; }
-.emoji-1F61E { background-position: -20040px 0px; }
-.emoji-1F61F { background-position: -20060px 0px; }
-.emoji-1F620 { background-position: -20080px 0px; }
-.emoji-1F621 { background-position: -20100px 0px; }
-.emoji-1F622 { background-position: -20120px 0px; }
-.emoji-1F623 { background-position: -20140px 0px; }
-.emoji-1F624 { background-position: -20160px 0px; }
-.emoji-1F625 { background-position: -20180px 0px; }
-.emoji-1F626 { background-position: -20200px 0px; }
-.emoji-1F627 { background-position: -20220px 0px; }
-.emoji-1F628 { background-position: -20240px 0px; }
-.emoji-1F629 { background-position: -20260px 0px; }
-.emoji-1F62A { background-position: -20280px 0px; }
-.emoji-1F62B { background-position: -20300px 0px; }
-.emoji-1F62C { background-position: -20320px 0px; }
-.emoji-1F62D { background-position: -20340px 0px; }
-.emoji-1F62E { background-position: -20360px 0px; }
-.emoji-1F62F { background-position: -20380px 0px; }
-.emoji-1F630 { background-position: -20400px 0px; }
-.emoji-1F631 { background-position: -20420px 0px; }
-.emoji-1F632 { background-position: -20440px 0px; }
-.emoji-1F633 { background-position: -20460px 0px; }
-.emoji-1F634 { background-position: -20480px 0px; }
-.emoji-1F635 { background-position: -20500px 0px; }
-.emoji-1F636 { background-position: -20520px 0px; }
-.emoji-1F637 { background-position: -20540px 0px; }
-.emoji-1F638 { background-position: -20560px 0px; }
-.emoji-1F639 { background-position: -20580px 0px; }
-.emoji-1F63A { background-position: -20600px 0px; }
-.emoji-1F63B { background-position: -20620px 0px; }
-.emoji-1F63C { background-position: -20640px 0px; }
-.emoji-1F63D { background-position: -20660px 0px; }
-.emoji-1F63E { background-position: -20680px 0px; }
-.emoji-1F63F { background-position: -20700px 0px; }
-.emoji-1F640 { background-position: -20720px 0px; }
-.emoji-1F641 { background-position: -20740px 0px; }
-.emoji-1F642 { background-position: -20760px 0px; }
-.emoji-1F645 { background-position: -20780px 0px; }
-.emoji-1F646 { background-position: -20800px 0px; }
-.emoji-1F647 { background-position: -20820px 0px; }
-.emoji-1F648 { background-position: -20840px 0px; }
-.emoji-1F649 { background-position: -20860px 0px; }
-.emoji-1F64A { background-position: -20880px 0px; }
-.emoji-1F64B { background-position: -20900px 0px; }
-.emoji-1F64C { background-position: -20920px 0px; }
-.emoji-1F64D { background-position: -20940px 0px; }
-.emoji-1F64E { background-position: -20960px 0px; }
-.emoji-1F64F { background-position: -20980px 0px; }
-.emoji-1F680 { background-position: -21000px 0px; }
-.emoji-1F681 { background-position: -21020px 0px; }
-.emoji-1F682 { background-position: -21040px 0px; }
-.emoji-1F683 { background-position: -21060px 0px; }
-.emoji-1F684 { background-position: -21080px 0px; }
-.emoji-1F685 { background-position: -21100px 0px; }
-.emoji-1F686 { background-position: -21120px 0px; }
-.emoji-1F687 { background-position: -21140px 0px; }
-.emoji-1F688 { background-position: -21160px 0px; }
-.emoji-1F689 { background-position: -21180px 0px; }
-.emoji-1F68A { background-position: -21200px 0px; }
-.emoji-1F68B { background-position: -21220px 0px; }
-.emoji-1F68C { background-position: -21240px 0px; }
-.emoji-1F68D { background-position: -21260px 0px; }
-.emoji-1F68E { background-position: -21280px 0px; }
-.emoji-1F68F { background-position: -21300px 0px; }
-.emoji-1F690 { background-position: -21320px 0px; }
-.emoji-1F691 { background-position: -21340px 0px; }
-.emoji-1F692 { background-position: -21360px 0px; }
-.emoji-1F693 { background-position: -21380px 0px; }
-.emoji-1F694 { background-position: -21400px 0px; }
-.emoji-1F695 { background-position: -21420px 0px; }
-.emoji-1F696 { background-position: -21440px 0px; }
-.emoji-1F697 { background-position: -21460px 0px; }
-.emoji-1F698 { background-position: -21480px 0px; }
-.emoji-1F699 { background-position: -21500px 0px; }
-.emoji-1F69A { background-position: -21520px 0px; }
-.emoji-1F69B { background-position: -21540px 0px; }
-.emoji-1F69C { background-position: -21560px 0px; }
-.emoji-1F69D { background-position: -21580px 0px; }
-.emoji-1F69E { background-position: -21600px 0px; }
-.emoji-1F69F { background-position: -21620px 0px; }
-.emoji-1F6A0 { background-position: -21640px 0px; }
-.emoji-1F6A1 { background-position: -21660px 0px; }
-.emoji-1F6A2 { background-position: -21680px 0px; }
-.emoji-1F6A3 { background-position: -21700px 0px; }
-.emoji-1F6A4 { background-position: -21720px 0px; }
-.emoji-1F6A5 { background-position: -21740px 0px; }
-.emoji-1F6A6 { background-position: -21760px 0px; }
-.emoji-1F6A7 { background-position: -21780px 0px; }
-.emoji-1F6A8 { background-position: -21800px 0px; }
-.emoji-1F6A9 { background-position: -21820px 0px; }
-.emoji-1F6AA { background-position: -21840px 0px; }
-.emoji-1F6AB { background-position: -21860px 0px; }
-.emoji-1F6AC { background-position: -21880px 0px; }
-.emoji-1F6AD { background-position: -21900px 0px; }
-.emoji-1F6AE { background-position: -21920px 0px; }
-.emoji-1F6AF { background-position: -21940px 0px; }
-.emoji-1F6B0 { background-position: -21960px 0px; }
-.emoji-1F6B1 { background-position: -21980px 0px; }
-.emoji-1F6B2 { background-position: -22000px 0px; }
-.emoji-1F6B3 { background-position: -22020px 0px; }
-.emoji-1F6B4 { background-position: -22040px 0px; }
-.emoji-1F6B5 { background-position: -22060px 0px; }
-.emoji-1F6B6 { background-position: -22080px 0px; }
-.emoji-1F6B7 { background-position: -22100px 0px; }
-.emoji-1F6B8 { background-position: -22120px 0px; }
-.emoji-1F6B9 { background-position: -22140px 0px; }
-.emoji-1F6BA { background-position: -22160px 0px; }
-.emoji-1F6BB { background-position: -22180px 0px; }
-.emoji-1F6BC { background-position: -22200px 0px; }
-.emoji-1F6BD { background-position: -22220px 0px; }
-.emoji-1F6BE { background-position: -22240px 0px; }
-.emoji-1F6BF { background-position: -22260px 0px; }
-.emoji-1F6C0 { background-position: -22280px 0px; }
-.emoji-1F6C1 { background-position: -22300px 0px; }
-.emoji-1F6C2 { background-position: -22320px 0px; }
-.emoji-1F6C3 { background-position: -22340px 0px; }
-.emoji-1F6C4 { background-position: -22360px 0px; }
-.emoji-1F6C5 { background-position: -22380px 0px; }
-.emoji-1F6C6 { background-position: -22400px 0px; }
-.emoji-1F6C7 { background-position: -22420px 0px; }
-.emoji-1F6C8 { background-position: -22440px 0px; }
-.emoji-1F6C9 { background-position: -22460px 0px; }
-.emoji-1F6CA { background-position: -22480px 0px; }
-.emoji-1F6CB { background-position: -22500px 0px; }
-.emoji-1F6CC { background-position: -22520px 0px; }
-.emoji-1F6CD { background-position: -22540px 0px; }
-.emoji-1F6CE { background-position: -22560px 0px; }
-.emoji-1F6CF { background-position: -22580px 0px; }
-.emoji-1F6E0 { background-position: -22600px 0px; }
-.emoji-1F6E1 { background-position: -22620px 0px; }
-.emoji-1F6E2 { background-position: -22640px 0px; }
-.emoji-1F6E3 { background-position: -22660px 0px; }
-.emoji-1F6E4 { background-position: -22680px 0px; }
-.emoji-1F6E5 { background-position: -22700px 0px; }
-.emoji-1F6E6 { background-position: -22720px 0px; }
-.emoji-1F6E7 { background-position: -22740px 0px; }
-.emoji-1F6E8 { background-position: -22760px 0px; }
-.emoji-1F6E9 { background-position: -22780px 0px; }
-.emoji-1F6EA { background-position: -22800px 0px; }
-.emoji-1F6EB { background-position: -22820px 0px; }
-.emoji-1F6EC { background-position: -22840px 0px; }
-.emoji-1F6F0 { background-position: -22860px 0px; }
-.emoji-1F6F1 { background-position: -22880px 0px; }
-.emoji-1F6F2 { background-position: -22900px 0px; }
-.emoji-1F6F3 { background-position: -22920px 0px; }
-.emoji-203C { background-position: -22940px 0px; }
-.emoji-2049 { background-position: -22960px 0px; }
-.emoji-2122 { background-position: -22980px 0px; }
-.emoji-2139 { background-position: -23000px 0px; }
-.emoji-2194 { background-position: -23020px 0px; }
-.emoji-2195 { background-position: -23040px 0px; }
-.emoji-2196 { background-position: -23060px 0px; }
-.emoji-2197 { background-position: -23080px 0px; }
-.emoji-2198 { background-position: -23100px 0px; }
-.emoji-2199 { background-position: -23120px 0px; }
-.emoji-21A9 { background-position: -23140px 0px; }
-.emoji-21AA { background-position: -23160px 0px; }
-.emoji-231A { background-position: -23180px 0px; }
-.emoji-231B { background-position: -23200px 0px; }
-.emoji-23E9 { background-position: -23220px 0px; }
-.emoji-23EA { background-position: -23240px 0px; }
-.emoji-23EB { background-position: -23260px 0px; }
-.emoji-23EC { background-position: -23280px 0px; }
-.emoji-23F0 { background-position: -23300px 0px; }
-.emoji-23F3 { background-position: -23320px 0px; }
-.emoji-24C2 { background-position: -23340px 0px; }
-.emoji-25AA { background-position: -23360px 0px; }
-.emoji-25AB { background-position: -23380px 0px; }
-.emoji-25B6 { background-position: -23400px 0px; }
-.emoji-25C0 { background-position: -23420px 0px; }
-.emoji-25FB { background-position: -23440px 0px; }
-.emoji-25FC { background-position: -23460px 0px; }
-.emoji-25FD { background-position: -23480px 0px; }
-.emoji-25FE { background-position: -23500px 0px; }
-.emoji-2600 { background-position: -23520px 0px; }
-.emoji-2601 { background-position: -23540px 0px; }
-.emoji-260E { background-position: -23560px 0px; }
-.emoji-2611 { background-position: -23580px 0px; }
-.emoji-2614 { background-position: -23600px 0px; }
-.emoji-2615 { background-position: -23620px 0px; }
-.emoji-261D { background-position: -23640px 0px; }
-.emoji-263A { background-position: -23660px 0px; }
-.emoji-2648 { background-position: -23680px 0px; }
-.emoji-2649 { background-position: -23700px 0px; }
-.emoji-264A { background-position: -23720px 0px; }
-.emoji-264B { background-position: -23740px 0px; }
-.emoji-264C { background-position: -23760px 0px; }
-.emoji-264D { background-position: -23780px 0px; }
-.emoji-264E { background-position: -23800px 0px; }
-.emoji-264F { background-position: -23820px 0px; }
-.emoji-2650 { background-position: -23840px 0px; }
-.emoji-2651 { background-position: -23860px 0px; }
-.emoji-2652 { background-position: -23880px 0px; }
-.emoji-2653 { background-position: -23900px 0px; }
-.emoji-2660 { background-position: -23920px 0px; }
-.emoji-2663 { background-position: -23940px 0px; }
-.emoji-2665 { background-position: -23960px 0px; }
-.emoji-2666 { background-position: -23980px 0px; }
-.emoji-2668 { background-position: -24000px 0px; }
-.emoji-267B { background-position: -24020px 0px; }
-.emoji-267F { background-position: -24040px 0px; }
-.emoji-2693 { background-position: -24060px 0px; }
-.emoji-26A0 { background-position: -24080px 0px; }
-.emoji-26A1 { background-position: -24100px 0px; }
-.emoji-26AA { background-position: -24120px 0px; }
-.emoji-26AB { background-position: -24140px 0px; }
-.emoji-26BD { background-position: -24160px 0px; }
-.emoji-26BE { background-position: -24180px 0px; }
-.emoji-26C4 { background-position: -24200px 0px; }
-.emoji-26C5 { background-position: -24220px 0px; }
-.emoji-26CE { background-position: -24240px 0px; }
-.emoji-26D4 { background-position: -24260px 0px; }
-.emoji-26EA { background-position: -24280px 0px; }
-.emoji-26F2 { background-position: -24300px 0px; }
-.emoji-26F3 { background-position: -24320px 0px; }
-.emoji-26F5 { background-position: -24340px 0px; }
-.emoji-26FA { background-position: -24360px 0px; }
-.emoji-26FD { background-position: -24380px 0px; }
-.emoji-2702 { background-position: -24400px 0px; }
-.emoji-2705 { background-position: -24420px 0px; }
-.emoji-2708 { background-position: -24440px 0px; }
-.emoji-2709 { background-position: -24460px 0px; }
-.emoji-270A { background-position: -24480px 0px; }
-.emoji-270B { background-position: -24500px 0px; }
-.emoji-270C { background-position: -24520px 0px; }
-.emoji-270F { background-position: -24540px 0px; }
-.emoji-2712 { background-position: -24560px 0px; }
-.emoji-2714 { background-position: -24580px 0px; }
-.emoji-2716 { background-position: -24600px 0px; }
-.emoji-2728 { background-position: -24620px 0px; }
-.emoji-2733 { background-position: -24640px 0px; }
-.emoji-2734 { background-position: -24660px 0px; }
-.emoji-2744 { background-position: -24680px 0px; }
-.emoji-2747 { background-position: -24700px 0px; }
-.emoji-274C { background-position: -24720px 0px; }
-.emoji-274E { background-position: -24740px 0px; }
-.emoji-2753 { background-position: -24760px 0px; }
-.emoji-2754 { background-position: -24780px 0px; }
-.emoji-2755 { background-position: -24800px 0px; }
-.emoji-2757 { background-position: -24820px 0px; }
-.emoji-2764 { background-position: -24840px 0px; }
-.emoji-2795 { background-position: -24860px 0px; }
-.emoji-2796 { background-position: -24880px 0px; }
-.emoji-2797 { background-position: -24900px 0px; }
-.emoji-27A1 { background-position: -24920px 0px; }
-.emoji-27B0 { background-position: -24940px 0px; }
-.emoji-27BF { background-position: -24960px 0px; }
-.emoji-2934 { background-position: -24980px 0px; }
-.emoji-2935 { background-position: -25000px 0px; }
-.emoji-2B05 { background-position: -25020px 0px; }
-.emoji-2B06 { background-position: -25040px 0px; }
-.emoji-2B07 { background-position: -25060px 0px; }
-.emoji-2B1B { background-position: -25080px 0px; }
-.emoji-2B1C { background-position: -25100px 0px; }
-.emoji-2B50 { background-position: -25120px 0px; }
-.emoji-2B55 { background-position: -25140px 0px; }
-.emoji-3030 { background-position: -25160px 0px; }
-.emoji-303D { background-position: -25180px 0px; }
-.emoji-3297 { background-position: -25200px 0px; }
-.emoji-3299 { background-position: -25220px 0px; } \ No newline at end of file
+ @media only screen and (-webkit-min-device-pixel-ratio: 2),
+ only screen and (min--moz-device-pixel-ratio: 2),
+ only screen and (-o-min-device-pixel-ratio: 2/1),
+ only screen and (min-device-pixel-ratio: 2),
+ only screen and (min-resolution: 192dpi),
+ only screen and (min-resolution: 2dppx) {
+ background-image: image-url('emoji@2x.png');
+ background-size: 840px 820px;
+ }
+}
diff --git a/app/assets/stylesheets/pages/issuable.scss b/app/assets/stylesheets/pages/issuable.scss
index 9d5dc42b6cc..1310e6ad7c7 100644
--- a/app/assets/stylesheets/pages/issuable.scss
+++ b/app/assets/stylesheets/pages/issuable.scss
@@ -64,7 +64,6 @@
// This prevents the mess when resizing the sidebar
// of elements repositioning themselves..
width: $gutter_inner_width;
- overflow-x: hidden;
// --
&:first-child {
@@ -80,14 +79,21 @@
display: inline-block;
}
+ .select2-container span {
+ margin-top: 0;
+ }
+
.issuable-count {
}
.gutter-toggle {
margin-left: 20px;
- border-left: 1px solid $border-gray-light;
padding-left: 10px;
+
+ &:hover {
+ color: $gray-darkest;
+ }
}
}
@@ -145,15 +151,13 @@
}
}
-
.right-sidebar {
position: fixed;
top: 58px;
+ bottom: 0;
right: 0;
- height: 100%;
- transition-duration: .3s;
+ transition: width .3s;
background: $gray-light;
- overflow: scroll;
padding: 10px 20px;
&.right-sidebar-expanded {
@@ -162,6 +166,14 @@
hr {
display: none;
}
+
+ .sidebar-collapsed-icon {
+ display: none;
+ }
+
+ .gutter-toggle {
+ border-left: 1px solid $border-gray-light;
+ }
}
.subscribe-button {
@@ -171,9 +183,15 @@
}
&.right-sidebar-collapsed {
+ /* Extra small devices (phones, less than 768px) */
+ display: none;
+ /* Small devices (tablets, 768px and up) */
+ @media (min-width: $screen-sm-min) {
+ display: block
+ }
+
width: $sidebar_collapsed_width;
padding-top: 0;
- overflow-x: hidden;
hr {
margin: 0;
@@ -184,38 +202,25 @@
}
.block {
- border-bottom: none;
+ width: $sidebar_collapsed_width - 1px;
+ margin-left: -19px;
padding: 15px 0 0 0;
+ border-bottom: none;
+ overflow: hidden;
}
- }
- .btn {
- background: $gray-normal;
- border: 1px solid $border-gray-normal;
- }
-
- &.right-sidebar-collapsed {
- .issuable-count,
- .issuable-nav,
- .assignee > *,
- .milestone > *,
- .labels > *,
- .participants > *,
- .light > *,
- .project-reference > * {
+ .hide-collapsed {
display: none;
}
.gutter-toggle {
- margin-left: -$gutter_inner_width + 4;
+ margin-left: -36px;
}
.sidebar-collapsed-icon {
display: block;
- float: left;
- width: 62px;
+ width: 100%;
text-align: center;
- margin-left: -19px;
padding-bottom: 10px;
color: #999999;
@@ -223,19 +228,37 @@
display: block;
margin-top: 0;
}
- }
+ .btn-clipboard {
+ border: none;
+
+ &:hover {
+ background: transparent;
+ }
+
+ i {
+ color: #999999;
+ }
+ }
+ }
}
- &.right-sidebar-expanded {
- .sidebar-collapsed-icon {
- display: none;
+ .btn {
+ background: $gray-normal;
+ border: 1px solid $border-gray-normal;
+ &:hover {
+ background: $gray-dark;
+ border: 1px solid $border-gray-dark;
}
}
}
+.btn-default.gutter-toggle {
+ margin-top: 4px;
+}
+
.detail-page-description {
small {
color: $gray-darkest;
}
-} \ No newline at end of file
+}
diff --git a/app/assets/stylesheets/pages/issues.scss b/app/assets/stylesheets/pages/issues.scss
index 8694bd654a7..1b686c58eaf 100644
--- a/app/assets/stylesheets/pages/issues.scss
+++ b/app/assets/stylesheets/pages/issues.scss
@@ -4,13 +4,7 @@
position: relative;
.issue-title {
- margin-bottom: 5px;
- font-size: $list-font-size;
- font-weight: 600;
- }
-
- .issue-info {
- color: $gl-gray;
+ margin-bottom: 2px;
}
.issue-check {
@@ -105,18 +99,17 @@ form.edit-issue {
.btn {
width: 100%;
- margin-top: -1px;
&:first-child:not(:last-child) {
- border-radius: 4px 4px 0 0;
+
}
&:not(:first-child):not(:last-child) {
- border-radius: 0;
+ margin-top: 10px;
}
&:last-child:not(:first-child) {
- border-radius: 0 0 4px 4px;
+ margin-top: 10px;
}
}
}
@@ -140,3 +133,11 @@ form.edit-issue {
color: $secondary-text;
margin-left: 52px;
}
+
+.editor-details {
+ display: block;
+
+ @media (min-width: $screen-sm-min) {
+ display: inline-block;
+ }
+} \ No newline at end of file
diff --git a/app/assets/stylesheets/pages/labels.scss b/app/assets/stylesheets/pages/labels.scss
index d1590e42fcb..1c78aafdb87 100644
--- a/app/assets/stylesheets/pages/labels.scss
+++ b/app/assets/stylesheets/pages/labels.scss
@@ -9,7 +9,7 @@
}
}
-.manage-labels-list {
+.label-row {
.label {
padding: 9px;
font-size: 14px;
diff --git a/app/assets/stylesheets/pages/login.scss b/app/assets/stylesheets/pages/login.scss
index f9c6f1b39f9..61bec02f6c5 100644
--- a/app/assets/stylesheets/pages/login.scss
+++ b/app/assets/stylesheets/pages/login.scss
@@ -8,6 +8,10 @@
max-width: none;
}
+ .flash-container {
+ margin-bottom: $gl-padding;
+ }
+
.brand-holder {
font-size: 18px;
line-height: 1.5;
diff --git a/app/assets/stylesheets/pages/merge_requests.scss b/app/assets/stylesheets/pages/merge_requests.scss
index f033ff15f88..2772623f4bd 100644
--- a/app/assets/stylesheets/pages/merge_requests.scss
+++ b/app/assets/stylesheets/pages/merge_requests.scss
@@ -148,15 +148,8 @@
position: relative;
.merge-request-title {
- margin-bottom: 5px;
- font-size: $list-font-size;
- font-weight: 600;
- }
-
- .merge-request-info {
- color: $gl-gray;
+ margin-bottom: 2px;
}
-
}
.merge-request-labels {
@@ -236,4 +229,4 @@
}
}
}
-} \ No newline at end of file
+}
diff --git a/app/assets/stylesheets/pages/milestone.scss b/app/assets/stylesheets/pages/milestone.scss
index e80dc9e84a1..d0e72a4422c 100644
--- a/app/assets/stylesheets/pages/milestone.scss
+++ b/app/assets/stylesheets/pages/milestone.scss
@@ -11,3 +11,56 @@ li.milestone {
height: 6px;
}
}
+
+.milestone-content {
+ .issues-count {
+ margin-right: 17px;
+ float: right;
+ width: 105px;
+ }
+
+ .issuable-row {
+ .color-label {
+ border-radius: 2px;
+ padding: 3px !important;
+ margin-right: 7px;
+ }
+
+ // Issue title
+ span a {
+ color: rgba(0,0,0,0.64);
+ }
+ }
+}
+
+.milestone-summary {
+ margin-bottom: 25px;
+
+ .milestone-stat {
+ margin-right: 10px;
+ }
+
+ .remaining-days {
+ color: $orange-light;
+ }
+}
+
+.issues-sortable-list, .merge_requests-sortable-list {
+ .issuable-detail {
+ display: block;
+ margin-top: 7px;
+
+ .issuable-number {
+ color: rgba(0,0,0,0.44);
+ margin-right: 5px;
+ }
+ .avatar {
+ float: none;
+ }
+ }
+}
+
+.milestone-detail {
+ border-bottom: 1px solid $border-color;
+ padding: 20px 0;
+}
diff --git a/app/assets/stylesheets/pages/notes.scss b/app/assets/stylesheets/pages/notes.scss
index 19ead07c06a..d5f9852ebed 100644
--- a/app/assets/stylesheets/pages/notes.scss
+++ b/app/assets/stylesheets/pages/notes.scss
@@ -14,6 +14,18 @@ ul.notes {
margin: 0px;
padding: 0px;
+ .timeline-icon {
+ float: left;
+ }
+
+ .timeline-content {
+ margin-left: 55px;
+ }
+
+ .note_created_ago, .note-updated-at {
+ white-space: nowrap;
+ }
+
.system-note {
font-size: 14px;
padding-top: 10px;
@@ -151,6 +163,7 @@ ul.notes {
border-left: none;
&.notes_line {
+ vertical-align: middle;
text-align: center;
padding: 10px 0;
background: #FFF;
diff --git a/app/assets/stylesheets/pages/notifications.scss b/app/assets/stylesheets/pages/notifications.scss
index cc273f55222..94fbbef3c77 100644
--- a/app/assets/stylesheets/pages/notifications.scss
+++ b/app/assets/stylesheets/pages/notifications.scss
@@ -1,16 +1,18 @@
-.global-notifications-form .level-title {
- font-size: 15px;
- color: #333;
- font-weight: bold;
+.notification-list-item {
+ line-height: 34px;
}
-.notification-icon-holder {
- width: 20px;
- float: left;
+.notification {
+ position: relative;
+ top: 1px;
+
+ > .fa {
+ font-size: 18px;
+ }
}
.ns-part {
- color: $gl-primary;
+ color: $gl-text-green;
}
.ns-watch {
diff --git a/app/assets/stylesheets/pages/profile.scss b/app/assets/stylesheets/pages/profile.scss
index 95fc26a608a..4826b994e37 100644
--- a/app/assets/stylesheets/pages/profile.scss
+++ b/app/assets/stylesheets/pages/profile.scss
@@ -5,12 +5,24 @@
}
}
-.profile-avatar-form-option {
- hr {
- margin: 10px 0;
+.avatar-image {
+ @media (min-width: $screen-sm-min) {
+ float: left;
+ margin-bottom: 0;
}
}
+.avatar-file-name {
+ position: relative;
+ top: 2px;
+ display: inline-block;
+}
+
+.account-btn-link,
+.profile-settings-sidebar a {
+ color: $profile-settings-link-color;
+}
+
.oauth-buttons {
.btn-group {
margin-right: 10px;
@@ -42,6 +54,18 @@
}
}
+.account-well {
+ padding: 10px 10px;
+ background-color: $help-well-bg;
+ border: 1px solid $help-well-border;
+ border-radius: $border-radius-base;
+
+ ul {
+ padding-left: 20px;
+ margin-bottom: 0;
+ }
+}
+
.calendar-hint {
margin-top: -12px;
float: right;
@@ -51,9 +75,17 @@
.profile-link-holder {
display: inline;
+ a {
+ color: $blue-dark;
+ text-decoration: none;
+ }
+}
+
+// Middle dot divider between each element in a list of items.
+.middle-dot-divider {
&:after {
- content: "\00B7";
- padding: 0px 6px;
+ content: "\00B7"; // Middle Dot
+ padding: 0 6px;
font-weight: bold;
}
@@ -63,9 +95,83 @@
padding: 0;
}
}
+}
+
+.profile-user-bio {
+ // Limits the width of the user bio for readability.
+ max-width: 750px;
+ margin: auto;
+}
+
+.user-avatar-button {
+ .file-name {
+ display: inline-block;
+ padding-left: 10px;
+ }
+}
+
+.modal-profile-crop {
+ .modal-dialog {
+ width: 500px;
+ }
+
+ .modal-body {
+ p {
+ display: table;
+ margin: auto;
+ overflow: hidden;
+ }
+
+ img {
+ display: block;
+ max-width: 400px;
+ max-height: 400px;
+ }
+
+ .cropper-bg {
+ background: none;
+ }
+
+ .cropper-crop-box {
+ box-sizing: content-box;
+ border: 999px solid transparentize(#ccc, 0.5);
+ @include transform(translate(-999px, -999px));
+ }
+ }
+}
+
+@media (max-width: 520px) {
+ .modal-profile-crop .modal-dialog {
+ width: auto;
+ }
+}
+
+.key-list-item {
+ .key-list-item-info {
+ @media (min-width: $screen-sm-min) {
+ float: left;
+ }
+ }
+
+ .description {
+ white-space: nowrap;
+ text-overflow: ellipsis;
+ overflow: hidden;
+ }
+}
+.key-icon {
+ color: $ssh-key-icon-color;
+ font-size: $ssh-key-icon-size;
+ line-height: 42px;
+}
+
+.key-created-at {
+ line-height: 42px;
+}
+
+.profile-settings-content {
a {
- color: $blue-dark;
- text-decoration: none;
+ color: $profile-settings-link-color;
}
}
diff --git a/app/assets/stylesheets/pages/projects.scss b/app/assets/stylesheets/pages/projects.scss
index 046bae672fc..b1b76edfb32 100644
--- a/app/assets/stylesheets/pages/projects.scss
+++ b/app/assets/stylesheets/pages/projects.scss
@@ -32,6 +32,7 @@
.cover-controls {
.project-settings-dropdown {
margin-left: 10px;
+ display: inline-block;
}
}
@@ -48,10 +49,6 @@
}
}
- .project-home-dropdown {
- margin: 13px 0px 0;
- }
-
.notifications-btn {
margin-top: -28px;
@@ -73,24 +70,19 @@
font-weight: normal;
}
+ .visibility-icon {
+ display: inline-block;
+ margin-left: 5px;
+ font-size: 18px;
+ color: $gray;
+ }
+
p {
padding: 0 $gl-padding;
color: #5c5d5e;
}
}
- .visibility-level-label {
- @extend .btn;
- @extend .btn-gray;
-
- color: $gray;
- cursor: default;
-
- i {
- color: inherit;
- }
- }
-
.project-repo-buttons {
margin-top: 20px;
margin-bottom: 0px;
@@ -189,29 +181,6 @@
}
}
-.dropdown-menu {
- @include box-shadow(rgba(76, 86, 103, 0.247059) 0px 0px 1px 0px, rgba(31, 37, 50, 0.317647) 0px 2px 18px 0px);
- @include border-radius (0px);
-
- border: none;
- padding: 16px 0;
- font-size: 14px;
- font-weight: 100;
-
- li a {
- color: #5f697a;
- line-height: 30px;
-
- &:hover {
- background-color: #3084bb !important;
- }
- }
-
- i {
- margin-right: 8px;
- }
-}
-
.project-visibility-level-holder {
.radio {
margin-bottom: 10px;
@@ -401,15 +370,10 @@ pre.light-well {
.project-full-name {
@include str-truncated;
- font-weight: 600;
- color: #4c4e54;
}
- .project-controls {
- float: right;
- color: $gl-gray;
+ .controls {
line-height: 40px;
- color: #7f8fa4;
a:hover {
text-decoration: none;
@@ -419,16 +383,6 @@ pre.light-well {
margin-left: 10px;
}
}
-
- .project-description {
- color: #7f8fa4;
-
- p {
- @include str-truncated;
- margin-bottom: 0;
- color: #7f8fa4;
- }
- }
}
.bottom {
diff --git a/app/assets/stylesheets/pages/search.scss b/app/assets/stylesheets/pages/search.scss
index 3aaa96da609..84234b15c65 100644
--- a/app/assets/stylesheets/pages/search.scss
+++ b/app/assets/stylesheets/pages/search.scss
@@ -1,8 +1,12 @@
.search-results {
.search-result-row {
- border-bottom: 1px solid #DDD;
- padding-bottom: 15px;
- margin-bottom: 15px;
+ border-bottom: 1px solid $border-color;
+ padding-bottom: $gl-padding;
+ margin-bottom: $gl-padding;
+
+ &:last-child {
+ border-bottom: none;
+ }
}
}
diff --git a/app/assets/stylesheets/pages/snippets.scss b/app/assets/stylesheets/pages/snippets.scss
index 1430d01859d..0161642d871 100644
--- a/app/assets/stylesheets/pages/snippets.scss
+++ b/app/assets/stylesheets/pages/snippets.scss
@@ -2,30 +2,6 @@
padding: 2px;
}
-
-.snippet-row {
- .snippet-title {
- font-size: 15px;
- font-weight: bold;
- line-height: 20px;
- margin-bottom: 2px;
-
- .monospace {
- font-weight: normal;
- }
- }
-
- .snippet-info {
- color: #888;
- font-size: 13px;
- line-height: 24px;
-
- a {
- color: #888;
- }
- }
-}
-
.snippet-holder {
margin-bottom: -$gl-padding;
diff --git a/app/assets/stylesheets/pages/todos.scss b/app/assets/stylesheets/pages/todos.scss
new file mode 100644
index 00000000000..479c3c16d46
--- /dev/null
+++ b/app/assets/stylesheets/pages/todos.scss
@@ -0,0 +1,96 @@
+/**
+ * Dashboard Todos
+ *
+ */
+
+.navbar-nav {
+ li {
+ .badge.todos-pending-count {
+ background-color: #7f8fa4;
+ margin-top: -5px;
+ font-weight: normal;
+ }
+ }
+}
+
+.todo-item {
+ font-size: $gl-font-size;
+ padding-left: $gl-avatar-size + $gl-padding-top;
+ color: $secondary-text;
+
+ a {
+ color: #4c4e54;
+ }
+
+ .avatar {
+ margin-left: -($gl-avatar-size + $gl-padding-top);
+ }
+
+ .todo-title {
+ @include str-truncated(calc(100% - 174px));
+ font-weight: 600;
+
+ .author-name {
+ color: #333;
+ }
+ }
+
+ .todo-body {
+ margin-right: 174px;
+
+ .todo-note {
+ word-wrap: break-word;
+
+ .md {
+ color: #7f8fa4;
+ font-size: $gl-font-size;
+
+ p {
+ color: #5c5d5e;
+ }
+ }
+
+ pre {
+ border: none;
+ background: #f9f9f9;
+ border-radius: 0;
+ color: #777;
+ margin: 0 20px;
+ overflow: hidden;
+ }
+
+ .note-image-attach {
+ margin-top: 4px;
+ margin-left: 0px;
+ max-width: 200px;
+ float: none;
+ }
+
+ p:last-child {
+ margin-bottom: 0;
+ }
+ }
+ }
+}
+
+@media (max-width: $screen-xs-max) {
+ .todo-item {
+ padding-left: $gl-padding;
+
+ .todo-title {
+ white-space: normal;
+ overflow: visible;
+ max-width: 100%;
+ }
+
+ .avatar {
+ display: none;
+ }
+
+ .todo-body {
+ margin: 0;
+ border-left: 2px solid #DDD;
+ padding-left: 10px;
+ }
+ }
+}
diff --git a/app/assets/stylesheets/pages/tree.scss b/app/assets/stylesheets/pages/tree.scss
index c7411617cb3..ef63b010600 100644
--- a/app/assets/stylesheets/pages/tree.scss
+++ b/app/assets/stylesheets/pages/tree.scss
@@ -21,7 +21,7 @@
&:hover {
td {
- background: $hover;
+ background: $row-hover;
}
cursor: pointer;
}
diff --git a/app/assets/stylesheets/pages/ui_dev_kit.scss b/app/assets/stylesheets/pages/ui_dev_kit.scss
index 185f3622e64..05fa9312efb 100644
--- a/app/assets/stylesheets/pages/ui_dev_kit.scss
+++ b/app/assets/stylesheets/pages/ui_dev_kit.scss
@@ -3,4 +3,15 @@
margin: 35px 0 20px;
font-weight: bold;
}
+
+ .example {
+ &:before {
+ content: "Example";
+ color: #BBB;
+ }
+
+ padding: 15px;
+ border: 1px dashed #ddd;
+ margin-bottom: 15px;
+ }
}
diff --git a/app/assets/stylesheets/pages/wiki.scss b/app/assets/stylesheets/pages/wiki.scss
index cdf514197cb..dfaeba41cf6 100644
--- a/app/assets/stylesheets/pages/wiki.scss
+++ b/app/assets/stylesheets/pages/wiki.scss
@@ -4,8 +4,3 @@
margin-right: auto;
padding-right: 7px;
}
-
-.wiki-last-edit-by {
- font-size: 80%;
- font-weight: normal;
-}
diff --git a/app/controllers/admin/appearances_controller.rb b/app/controllers/admin/appearances_controller.rb
new file mode 100644
index 00000000000..26cf74e4849
--- /dev/null
+++ b/app/controllers/admin/appearances_controller.rb
@@ -0,0 +1,57 @@
+class Admin::AppearancesController < Admin::ApplicationController
+ before_action :set_appearance, except: :create
+
+ def show
+ end
+
+ def preview
+ end
+
+ def create
+ @appearance = Appearance.new(appearance_params)
+
+ if @appearance.save
+ redirect_to admin_appearances_path, notice: 'Appearance was successfully created.'
+ else
+ render action: 'show'
+ end
+ end
+
+ def update
+ if @appearance.update(appearance_params)
+ redirect_to admin_appearances_path, notice: 'Appearance was successfully updated.'
+ else
+ render action: 'show'
+ end
+ end
+
+ def logo
+ @appearance.remove_logo!
+
+ @appearance.save
+
+ redirect_to admin_appearances_path, notice: 'Logo was succesfully removed.'
+ end
+
+ def header_logos
+ @appearance.remove_header_logo!
+ @appearance.save
+
+ redirect_to admin_appearances_path, notice: 'Header logo was succesfully removed.'
+ end
+
+ private
+
+ # Use callbacks to share common setup or constraints between actions.
+ def set_appearance
+ @appearance = Appearance.last || Appearance.new
+ end
+
+ # Only allow a trusted parameter "white list" through.
+ def appearance_params
+ params.require(:appearance).permit(
+ :title, :description, :logo, :logo_cache, :header_logo, :header_logo_cache,
+ :updated_by
+ )
+ end
+end
diff --git a/app/controllers/admin/labels_controller.rb b/app/controllers/admin/labels_controller.rb
index 3b070e65d0d..d79ce2b10fe 100644
--- a/app/controllers/admin/labels_controller.rb
+++ b/app/controllers/admin/labels_controller.rb
@@ -53,6 +53,6 @@ class Admin::LabelsController < Admin::ApplicationController
end
def label_params
- params[:label].permit(:title, :color)
+ params[:label].permit(:title, :description, :color)
end
end
diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb
index 48b1f95acb9..fb74919ea23 100644
--- a/app/controllers/application_controller.rb
+++ b/app/controllers/application_controller.rb
@@ -25,7 +25,7 @@ class ApplicationController < ActionController::Base
helper_method :abilities, :can?, :current_application_settings
helper_method :import_sources_enabled?, :github_import_enabled?, :github_import_configured?, :gitlab_import_enabled?, :gitlab_import_configured?, :bitbucket_import_enabled?, :bitbucket_import_configured?, :gitorious_import_enabled?, :google_code_import_enabled?, :fogbugz_import_enabled?, :git_import_enabled?
- helper_method :repository
+ helper_method :repository, :can_collaborate_with_project?
rescue_from Encoding::CompatibilityError do |exception|
log_exception(exception)
@@ -164,7 +164,7 @@ class ApplicationController < ActionController::Base
end
def git_not_found!
- render html: "errors/git_not_found", layout: "errors", status: 404
+ render "errors/git_not_found.html", layout: "errors", status: 404
end
def method_missing(method_sym, *arguments, &block)
@@ -410,6 +410,13 @@ class ApplicationController < ActionController::Base
current_user.nil? && root_path == request.path
end
+ def can_collaborate_with_project?(project = nil)
+ project ||= @project
+
+ can?(current_user, :push_code, project) ||
+ (current_user && current_user.already_forked?(project))
+ end
+
private
def set_default_sort
diff --git a/app/controllers/ci/projects_controller.rb b/app/controllers/ci/projects_controller.rb
index d1824b481d7..081e01a75e0 100644
--- a/app/controllers/ci/projects_controller.rb
+++ b/app/controllers/ci/projects_controller.rb
@@ -3,6 +3,7 @@ module Ci
before_action :project
before_action :authorize_read_project!, except: [:badge]
before_action :no_cache, only: [:badge]
+ skip_before_action :authenticate_user!, only: [:badge]
protect_from_forgery
def show
@@ -18,6 +19,7 @@ module Ci
#
def badge
return render_404 unless @project
+
image = Ci::ImageForBuildService.new.execute(@project, params)
send_file image.path, filename: image.name, disposition: 'inline', type:"image/svg+xml"
end
diff --git a/app/controllers/concerns/creates_commit.rb b/app/controllers/concerns/creates_commit.rb
index b9eb0a22f88..787416c17ab 100644
--- a/app/controllers/concerns/creates_commit.rb
+++ b/app/controllers/concerns/creates_commit.rb
@@ -13,17 +13,11 @@ module CreatesCommit
result = service.new(@tree_edit_project, current_user, commit_params).execute
if result[:status] == :success
- flash[:notice] = success_notice || "Your changes have been successfully committed."
-
- if create_merge_request?
- success_path = new_merge_request_path
- target = different_project? ? "project" : "branch"
- flash[:notice] << " You can now submit a merge request to get this change into the original #{target}."
- end
+ update_flash_notice(success_notice)
respond_to do |format|
- format.html { redirect_to success_path }
- format.json { render json: { message: "success", filePath: success_path } }
+ format.html { redirect_to final_success_path(success_path) }
+ format.json { render json: { message: "success", filePath: final_success_path(success_path) } }
end
else
flash[:alert] = result[:message]
@@ -41,14 +35,32 @@ module CreatesCommit
end
def authorize_edit_tree!
- return if can?(current_user, :push_code, project)
- return if current_user && current_user.already_forked?(project)
+ return if can_collaborate_with_project?
access_denied!
end
private
+ def update_flash_notice(success_notice)
+ flash[:notice] = success_notice || "Your changes have been successfully committed."
+
+ if create_merge_request?
+ if merge_request_exists?
+ flash[:notice] = nil
+ else
+ target = different_project? ? "project" : "branch"
+ flash[:notice] << " You can now submit a merge request to get this change into the original #{target}."
+ end
+ end
+ end
+
+ def final_success_path(success_path)
+ return success_path unless create_merge_request?
+
+ merge_request_exists? ? existing_merge_request_path : new_merge_request_path
+ end
+
def new_merge_request_path
new_namespace_project_merge_request_path(
@mr_source_project.namespace,
@@ -62,6 +74,19 @@ module CreatesCommit
)
end
+ def existing_merge_request_path
+ namespace_project_merge_request_path(@mr_target_project.namespace, @mr_target_project, @merge_request)
+ end
+
+ def merge_request_exists?
+ return @merge_request if defined?(@merge_request)
+
+ @merge_request = @mr_target_project.merge_requests.opened.find_by(
+ source_branch: @mr_source_branch,
+ target_branch: @mr_target_branch
+ )
+ end
+
def different_project?
@mr_source_project != @mr_target_project
end
@@ -75,7 +100,7 @@ module CreatesCommit
end
def set_commit_variables
- @mr_source_branch = @target_branch
+ @mr_source_branch ||= @target_branch
if can?(current_user, :push_code, @project)
# Edit file in this project
@@ -89,7 +114,7 @@ module CreatesCommit
else
# Merge request to this project
@mr_target_project = @project
- @mr_target_branch = @ref
+ @mr_target_branch ||= @ref
end
else
# Edit file in fork
@@ -97,7 +122,7 @@ module CreatesCommit
# Merge request from fork to this project
@mr_source_project = @tree_edit_project
@mr_target_project = @project
- @mr_target_branch = @ref
+ @mr_target_branch ||= @ref
end
end
end
diff --git a/app/controllers/concerns/issues_action.rb b/app/controllers/concerns/issues_action.rb
index effd4721949..ef8e74a4641 100644
--- a/app/controllers/concerns/issues_action.rb
+++ b/app/controllers/concerns/issues_action.rb
@@ -2,10 +2,12 @@ module IssuesAction
extend ActiveSupport::Concern
def issues
- @issues = get_issues_collection
+ @issues = get_issues_collection.non_archived
@issues = @issues.page(params[:page]).per(ApplicationController::PER_PAGE)
@issues = @issues.preload(:author, :project)
+ @label = @issuable_finder.labels.first
+
respond_to do |format|
format.html
format.atom { render layout: false }
diff --git a/app/controllers/concerns/merge_requests_action.rb b/app/controllers/concerns/merge_requests_action.rb
index f7a25111db9..9c49596bd0b 100644
--- a/app/controllers/concerns/merge_requests_action.rb
+++ b/app/controllers/concerns/merge_requests_action.rb
@@ -2,8 +2,10 @@ module MergeRequestsAction
extend ActiveSupport::Concern
def merge_requests
- @merge_requests = get_merge_requests_collection
+ @merge_requests = get_merge_requests_collection.non_archived
@merge_requests = @merge_requests.page(params[:page]).per(ApplicationController::PER_PAGE)
@merge_requests = @merge_requests.preload(:author, :target_project)
+
+ @label = @issuable_finder.labels.first
end
end
diff --git a/app/controllers/dashboard/projects_controller.rb b/app/controllers/dashboard/projects_controller.rb
index 2df6924b13d..dc880b634e5 100644
--- a/app/controllers/dashboard/projects_controller.rb
+++ b/app/controllers/dashboard/projects_controller.rb
@@ -6,7 +6,7 @@ class Dashboard::ProjectsController < Dashboard::ApplicationController
@projects = @projects.sort(@sort = params[:sort])
@projects = @projects.includes(:namespace)
- terms = params['filter_projects']
+ terms = params[:filter_projects]
if terms.present?
@projects = @projects.search(terms)
@@ -31,11 +31,11 @@ class Dashboard::ProjectsController < Dashboard::ApplicationController
end
def starred
- @projects = current_user.starred_projects
+ @projects = current_user.starred_projects.sorted_by_activity
@projects = @projects.includes(:namespace, :forked_from_project, :tags)
@projects = @projects.sort(@sort = params[:sort])
- terms = params['filter_projects']
+ terms = params[:filter_projects]
if terms.present?
@projects = @projects.search(terms)
diff --git a/app/controllers/dashboard/todos_controller.rb b/app/controllers/dashboard/todos_controller.rb
new file mode 100644
index 00000000000..43cf8fa71af
--- /dev/null
+++ b/app/controllers/dashboard/todos_controller.rb
@@ -0,0 +1,35 @@
+class Dashboard::TodosController < Dashboard::ApplicationController
+ before_action :find_todos, only: [:index, :destroy_all]
+
+ def index
+ @todos = @todos.page(params[:page]).per(PER_PAGE)
+ end
+
+ def destroy
+ todo.done!
+
+ respond_to do |format|
+ format.html { redirect_to dashboard_todos_path, notice: 'Todo was successfully marked as done.' }
+ format.js { render nothing: true }
+ end
+ end
+
+ def destroy_all
+ @todos.each(&:done!)
+
+ respond_to do |format|
+ format.html { redirect_to dashboard_todos_path, notice: 'All todos were marked as done.' }
+ format.js { render nothing: true }
+ end
+ end
+
+ private
+
+ def todo
+ @todo ||= current_user.todos.find(params[:id])
+ end
+
+ def find_todos
+ @todos = TodosFinder.new(current_user, params).execute
+ end
+end
diff --git a/app/controllers/emojis_controller.rb b/app/controllers/emojis_controller.rb
new file mode 100644
index 00000000000..1bec5a7d27f
--- /dev/null
+++ b/app/controllers/emojis_controller.rb
@@ -0,0 +1,6 @@
+class EmojisController < ApplicationController
+ layout false
+
+ def index
+ end
+end
diff --git a/app/controllers/omniauth_callbacks_controller.rb b/app/controllers/omniauth_callbacks_controller.rb
index 9cf76521a0d..21135f7d607 100644
--- a/app/controllers/omniauth_callbacks_controller.rb
+++ b/app/controllers/omniauth_callbacks_controller.rb
@@ -42,6 +42,26 @@ class OmniauthCallbacksController < Devise::OmniauthCallbacksController
end
end
+ def saml
+ if current_user
+ log_audit_event(current_user, with: :saml)
+ # Update SAML identity if data has changed.
+ identity = current_user.identities.find_by(extern_uid: oauth['uid'], provider: :saml)
+ if identity.nil?
+ current_user.identities.create(extern_uid: oauth['uid'], provider: :saml)
+ redirect_to profile_account_path, notice: 'Authentication method updated'
+ else
+ redirect_to after_sign_in_path_for(current_user)
+ end
+ else
+ saml_user = Gitlab::Saml::User.new(oauth)
+ saml_user.save
+ @user = saml_user.gl_user
+
+ continue_login_process
+ end
+ end
+
def omniauth_error
@provider = params[:provider]
@error = params[:error]
@@ -65,25 +85,11 @@ class OmniauthCallbacksController < Devise::OmniauthCallbacksController
log_audit_event(current_user, with: oauth['provider'])
redirect_to profile_account_path, notice: 'Authentication method updated'
else
- @user = Gitlab::OAuth::User.new(oauth)
- @user.save
+ oauth_user = Gitlab::OAuth::User.new(oauth)
+ oauth_user.save
+ @user = oauth_user.gl_user
- # Only allow properly saved users to login.
- if @user.persisted? && @user.valid?
- log_audit_event(@user.gl_user, with: oauth['provider'])
- sign_in_and_redirect(@user.gl_user)
- else
- error_message =
- if @user.gl_user.errors.any?
- @user.gl_user.errors.map do |attribute, message|
- "#{attribute} #{message}"
- end.join(", ")
- else
- ''
- end
-
- redirect_to omniauth_error_path(oauth['provider'], error: error_message) and return
- end
+ continue_login_process
end
rescue Gitlab::OAuth::SignupDisabledError
label = Gitlab::OAuth::Provider.label_for(oauth['provider'])
@@ -104,6 +110,18 @@ class OmniauthCallbacksController < Devise::OmniauthCallbacksController
session[:service_tickets][provider] = ticket
end
+ def continue_login_process
+ # Only allow properly saved users to login.
+ if @user.persisted? && @user.valid?
+ log_audit_event(@user, with: oauth['provider'])
+ sign_in_and_redirect(@user)
+ else
+ error_message = @user.errors.full_messages.to_sentence
+
+ redirect_to omniauth_error_path(oauth['provider'], error: error_message) and return
+ end
+ end
+
def oauth
@oauth ||= request.env['omniauth.auth']
end
diff --git a/app/controllers/passwords_controller.rb b/app/controllers/passwords_controller.rb
index f74daff3bd0..a8575e037e4 100644
--- a/app/controllers/passwords_controller.rb
+++ b/app/controllers/passwords_controller.rb
@@ -23,6 +23,14 @@ class PasswordsController < Devise::PasswordsController
end
end
+ def update
+ super do |resource|
+ if resource.valid? && resource.require_password?
+ resource.update_attribute(:password_automatically_set, false)
+ end
+ end
+ end
+
protected
def resource_from_email
diff --git a/app/controllers/profiles/keys_controller.rb b/app/controllers/profiles/keys_controller.rb
index f3224148fda..b88c080352b 100644
--- a/app/controllers/profiles/keys_controller.rb
+++ b/app/controllers/profiles/keys_controller.rb
@@ -3,23 +3,21 @@ class Profiles::KeysController < Profiles::ApplicationController
def index
@keys = current_user.keys
+ @key = Key.new
end
def show
@key = current_user.keys.find(params[:id])
end
- def new
- @key = current_user.keys.new
- end
-
def create
@key = current_user.keys.new(key_params)
if @key.save
redirect_to profile_key_path(@key)
else
- render 'new'
+ @keys = current_user.keys.select(&:persisted?)
+ render :index
end
end
diff --git a/app/controllers/profiles/two_factor_auths_controller.rb b/app/controllers/profiles/two_factor_auths_controller.rb
index f3bfede4354..8f83fdd02bc 100644
--- a/app/controllers/profiles/two_factor_auths_controller.rb
+++ b/app/controllers/profiles/two_factor_auths_controller.rb
@@ -12,11 +12,13 @@ class Profiles::TwoFactorAuthsController < Profiles::ApplicationController
current_user.save! if current_user.changed?
- if two_factor_grace_period_expired?
- flash.now[:alert] = 'You must enable Two-factor Authentication for your account.'
- else
- grace_period_deadline = current_user.otp_grace_period_started_at + two_factor_grace_period.hours
- flash.now[:alert] = "You must enable Two-factor Authentication for your account before #{l(grace_period_deadline)}."
+ if two_factor_authentication_required?
+ if two_factor_grace_period_expired?
+ flash.now[:alert] = 'You must enable Two-factor Authentication for your account.'
+ else
+ grace_period_deadline = current_user.otp_grace_period_started_at + two_factor_grace_period.hours
+ flash.now[:alert] = "You must enable Two-factor Authentication for your account before #{l(grace_period_deadline)}."
+ end
end
@qr_code = build_qr_code
diff --git a/app/controllers/profiles_controller.rb b/app/controllers/profiles_controller.rb
index 28803164fcf..fa7a1148961 100644
--- a/app/controllers/profiles_controller.rb
+++ b/app/controllers/profiles_controller.rb
@@ -65,6 +65,9 @@ class ProfilesController < Profiles::ApplicationController
def user_params
params.require(:user).permit(
+ :avatar_crop_x,
+ :avatar_crop_y,
+ :avatar_crop_size,
:avatar,
:bio,
:email,
diff --git a/app/controllers/projects/avatars_controller.rb b/app/controllers/projects/avatars_controller.rb
index f7e6bb34443..a6bebc46b06 100644
--- a/app/controllers/projects/avatars_controller.rb
+++ b/app/controllers/projects/avatars_controller.rb
@@ -1,13 +1,18 @@
class Projects::AvatarsController < Projects::ApplicationController
+ include BlobHelper
+
before_action :project
def show
@blob = @repository.blob_at_branch('master', @project.avatar_in_git)
if @blob
headers['X-Content-Type-Options'] = 'nosniff'
+
+ return if cached_blob?
+
headers.store(*Gitlab::Workhorse.send_git_blob(@repository, @blob))
headers['Content-Disposition'] = 'inline'
- headers['Content-Type'] = @blob.content_type
+ headers['Content-Type'] = safe_content_type(@blob)
head :ok # 'render nothing: true' messes up the Content-Type
else
render_404
diff --git a/app/controllers/projects/badges_controller.rb b/app/controllers/projects/badges_controller.rb
index a4dd94b941c..dc9c96df003 100644
--- a/app/controllers/projects/badges_controller.rb
+++ b/app/controllers/projects/badges_controller.rb
@@ -1,4 +1,6 @@
class Projects::BadgesController < Projects::ApplicationController
+ before_action :set_no_cache
+
def build
respond_to do |format|
format.html { render_404 }
@@ -8,4 +10,15 @@ class Projects::BadgesController < Projects::ApplicationController
end
end
end
+
+ private
+
+ def set_no_cache
+ expires_now
+
+ # Add some deprecated headers for older agents
+ #
+ response.headers['Pragma'] = 'no-cache'
+ response.headers['Expires'] = 'Fri, 01 Jan 1990 00:00:00 GMT'
+ end
end
diff --git a/app/controllers/projects/blob_controller.rb b/app/controllers/projects/blob_controller.rb
index 495a432347e..cd8b2911674 100644
--- a/app/controllers/projects/blob_controller.rb
+++ b/app/controllers/projects/blob_controller.rb
@@ -87,7 +87,7 @@ class Projects::BlobController < Projects::ApplicationController
private
def blob
- @blob ||= @repository.blob_at(@commit.id, @path)
+ @blob ||= Blob.decorate(@repository.blob_at(@commit.id, @path))
if @blob
@blob
diff --git a/app/controllers/projects/builds_controller.rb b/app/controllers/projects/builds_controller.rb
index ec379c53b8f..f159e169f6d 100644
--- a/app/controllers/projects/builds_controller.rb
+++ b/app/controllers/projects/builds_controller.rb
@@ -56,6 +56,12 @@ class Projects::BuildsController < Projects::ApplicationController
render json: @build.to_json(only: [:status, :id, :sha, :coverage], methods: :sha)
end
+ def erase
+ @build.erase(erased_by: current_user)
+ redirect_to namespace_project_build_path(project.namespace, project, @build),
+ notice: "Build has been sucessfully erased!"
+ end
+
private
def build
diff --git a/app/controllers/projects/commit_controller.rb b/app/controllers/projects/commit_controller.rb
index 21f4d9f44ec..576fa3cedb2 100644
--- a/app/controllers/projects/commit_controller.rb
+++ b/app/controllers/projects/commit_controller.rb
@@ -2,6 +2,9 @@
#
# Not to be confused with CommitsController, plural.
class Projects::CommitController < Projects::ApplicationController
+ include CreatesCommit
+ include DiffHelper
+
# Authorize
before_action :require_non_empty_project
before_action :authorize_download_code!, except: [:cancel_builds, :retry_builds]
@@ -9,10 +12,9 @@ class Projects::CommitController < Projects::ApplicationController
before_action :authorize_read_commit_status!, only: [:builds]
before_action :commit
before_action :define_show_vars, only: [:show, :builds]
+ before_action :authorize_edit_tree!, only: [:revert]
def show
- return git_not_found! unless @commit
-
apply_diff_view_cookie!
@line_notes = commit.notes.inline
@@ -57,8 +59,37 @@ class Projects::CommitController < Projects::ApplicationController
render layout: false
end
+ def revert
+ assign_revert_commit_vars
+
+ return render_404 if @target_branch.blank?
+
+ create_commit(Commits::RevertService, success_notice: "The #{revert_type_title} has been successfully reverted.",
+ success_path: successful_revert_path, failure_path: failed_revert_path)
+ end
+
private
+ def revert_type_title
+ @commit.merged_merge_request ? 'merge request' : 'commit'
+ end
+
+ def successful_revert_path
+ return referenced_merge_request_url if @commit.merged_merge_request
+
+ namespace_project_commits_url(@project.namespace, @project, @target_branch)
+ end
+
+ def failed_revert_path
+ return referenced_merge_request_url if @commit.merged_merge_request
+
+ namespace_project_commit_url(@project.namespace, @project, params[:id])
+ end
+
+ def referenced_merge_request_url
+ namespace_project_merge_request_url(@project.namespace, @project, @commit.merged_merge_request)
+ end
+
def commit
@commit ||= @project.commit(params[:id])
end
@@ -68,15 +99,27 @@ class Projects::CommitController < Projects::ApplicationController
end
def define_show_vars
- if params[:w].to_i == 1
- @diffs = commit.diffs({ ignore_whitespace_change: true })
- else
- @diffs = commit.diffs
- end
+ return git_not_found! unless commit
+
+ opts = diff_options
+ opts[:ignore_whitespace_change] = true if params[:format] == 'diff'
+ @diffs = commit.diffs(opts)
@diff_refs = [commit.parent || commit, commit]
@notes_count = commit.notes.count
@statuses = ci_commit.statuses if ci_commit
end
+
+ def assign_revert_commit_vars
+ @commit = project.commit(params[:id])
+ @target_branch = params[:target_branch]
+ @mr_source_branch = @commit.revert_branch_name
+ @mr_target_branch = @target_branch
+ @commit_params = {
+ commit: @commit,
+ revert_type_title: revert_type_title,
+ create_merge_request: params[:create_merge_request].present? || different_project?
+ }
+ end
end
diff --git a/app/controllers/projects/compare_controller.rb b/app/controllers/projects/compare_controller.rb
index dc5d217f3e4..671d5c23024 100644
--- a/app/controllers/projects/compare_controller.rb
+++ b/app/controllers/projects/compare_controller.rb
@@ -1,6 +1,8 @@
require 'addressable/uri'
class Projects::CompareController < Projects::ApplicationController
+ include DiffHelper
+
# Authorize
before_action :require_non_empty_project
before_action :authorize_download_code!
@@ -11,16 +13,14 @@ class Projects::CompareController < Projects::ApplicationController
end
def show
- diff_options = { ignore_whitespace_change: true } if params[:w] == '1'
-
- compare_result = CompareService.new.
+ compare = CompareService.new.
execute(@project, @head_ref, @project, @base_ref, diff_options)
- if compare_result
- @commits = Commit.decorate(compare_result.commits, @project)
- @diffs = compare_result.diffs
+ if compare
+ @commits = Commit.decorate(compare.commits, @project)
@commit = @project.commit(@head_ref)
@base_commit = @project.merge_base_commit(@base_ref, @head_ref)
+ @diffs = compare.diffs(diff_options)
@diff_refs = [@base_commit, @commit]
@line_notes = []
end
diff --git a/app/controllers/projects/forks_controller.rb b/app/controllers/projects/forks_controller.rb
index e61e01c4a59..7b202f3862f 100644
--- a/app/controllers/projects/forks_controller.rb
+++ b/app/controllers/projects/forks_controller.rb
@@ -4,11 +4,25 @@ class Projects::ForksController < Projects::ApplicationController
before_action :authorize_download_code!
def index
- @sort = params[:sort] || 'id_desc'
- @all_forks = project.forks.includes(:creator).order_by(@sort)
+ base_query = project.forks.includes(:creator)
- @public_forks, @protected_forks = @all_forks.partition do |project|
- can?(current_user, :read_project, project)
+ @forks = base_query.merge(ProjectsFinder.new.execute(current_user))
+ @total_forks_count = base_query.size
+ @private_forks_count = @total_forks_count - @forks.size
+ @public_forks_count = @total_forks_count - @private_forks_count
+
+ @sort = params[:sort] || 'id_desc'
+ @forks = @forks.search(params[:filter_projects]) if params[:filter_projects].present?
+ @forks = @forks.order_by(@sort).page(params[:page]).per(PER_PAGE)
+
+ respond_to do |format|
+ format.html
+
+ format.json do
+ render json: {
+ html: view_to_html_string("projects/forks/_projects", projects: @forks)
+ }
+ end
end
end
@@ -32,7 +46,7 @@ class Projects::ForksController < Projects::ApplicationController
if continue_params
redirect_to continue_params[:to], notice: continue_params[:notice]
else
- redirect_to namespace_project_path(@forked_project.namespace, @forked_project), notice: "The project was successfully forked."
+ redirect_to namespace_project_path(@forked_project.namespace, @forked_project), notice: "The project '#{@forked_project.name}' was successfully forked."
end
end
else
diff --git a/app/controllers/projects/imports_controller.rb b/app/controllers/projects/imports_controller.rb
index 07f355c35b1..196996f1752 100644
--- a/app/controllers/projects/imports_controller.rb
+++ b/app/controllers/projects/imports_controller.rb
@@ -3,6 +3,7 @@ class Projects::ImportsController < Projects::ApplicationController
before_action :authorize_admin_project!
before_action :require_no_repo, only: [:new, :create]
before_action :redirect_if_progress, only: [:new, :create]
+ before_action :redirect_if_no_import, only: :show
def new
end
@@ -63,14 +64,19 @@ class Projects::ImportsController < Projects::ApplicationController
def require_no_repo
if @project.repository_exists?
- redirect_to(namespace_project_path(@project.namespace, @project))
+ redirect_to namespace_project_path(@project.namespace, @project)
end
end
def redirect_if_progress
if @project.import_in_progress?
- redirect_to namespace_project_import_path(@project.namespace, @project) &&
- return
+ redirect_to namespace_project_import_path(@project.namespace, @project)
+ end
+ end
+
+ def redirect_if_no_import
+ if @project.repository_exists? && @project.no_import?
+ redirect_to namespace_project_path(@project.namespace, @project)
end
end
end
diff --git a/app/controllers/projects/issues_controller.rb b/app/controllers/projects/issues_controller.rb
index 68244883803..67faa1e4437 100644
--- a/app/controllers/projects/issues_controller.rb
+++ b/app/controllers/projects/issues_controller.rb
@@ -32,6 +32,7 @@ class Projects::IssuesController < Projects::ApplicationController
end
@issues = @issues.page(params[:page]).per(PER_PAGE)
+ @label = @project.labels.find_by(title: params[:label_name])
respond_to do |format|
format.html
diff --git a/app/controllers/projects/labels_controller.rb b/app/controllers/projects/labels_controller.rb
index 86d6e3e0f6b..ecac3c395ec 100644
--- a/app/controllers/projects/labels_controller.rb
+++ b/app/controllers/projects/labels_controller.rb
@@ -69,7 +69,7 @@ class Projects::LabelsController < Projects::ApplicationController
end
def label_params
- params.require(:label).permit(:title, :color)
+ params.require(:label).permit(:title, :description, :color)
end
def label
diff --git a/app/controllers/projects/merge_requests_controller.rb b/app/controllers/projects/merge_requests_controller.rb
index 9d588c370aa..03ba289eb94 100644
--- a/app/controllers/projects/merge_requests_controller.rb
+++ b/app/controllers/projects/merge_requests_controller.rb
@@ -1,4 +1,6 @@
class Projects::MergeRequestsController < Projects::ApplicationController
+ include DiffHelper
+
before_action :module_enabled
before_action :merge_request, only: [
:edit, :update, :show, :diffs, :commits, :builds, :merge, :merge_check,
@@ -34,6 +36,8 @@ class Projects::MergeRequestsController < Projects::ApplicationController
@merge_requests = @merge_requests.page(params[:page]).per(PER_PAGE)
@merge_requests = @merge_requests.preload(:target_project)
+ @label = @project.labels.find_by(title: params[:label_name])
+
respond_to do |format|
format.html
format.json do
@@ -109,7 +113,7 @@ class Projects::MergeRequestsController < Projects::ApplicationController
@commits = @merge_request.compare_commits.reverse
@commit = @merge_request.last_commit
@base_commit = @merge_request.diff_base_commit
- @diffs = @merge_request.compare_diffs
+ @diffs = @merge_request.compare.diffs(diff_options) if @merge_request.compare
@ci_commit = @merge_request.ci_commit
@statuses = @ci_commit.statuses if @ci_commit
@@ -179,6 +183,8 @@ class Projects::MergeRequestsController < Projects::ApplicationController
return
end
+ TodoService.new.merge_merge_request(merge_request, current_user)
+
@merge_request.update(merge_error: nil)
if params[:merge_when_build_succeeds].present? && @merge_request.ci_commit && @merge_request.ci_commit.active?
diff --git a/app/controllers/projects/milestones_controller.rb b/app/controllers/projects/milestones_controller.rb
index a5c4ef1c7c7..da46731d945 100644
--- a/app/controllers/projects/milestones_controller.rb
+++ b/app/controllers/projects/milestones_controller.rb
@@ -32,9 +32,6 @@ class Projects::MilestonesController < Projects::ApplicationController
end
def show
- @issues = @milestone.issues
- @users = @milestone.participants.uniq
- @merge_requests = @milestone.merge_requests
end
def create
diff --git a/app/controllers/projects/raw_controller.rb b/app/controllers/projects/raw_controller.rb
index 87b4d08da0e..10de0e60530 100644
--- a/app/controllers/projects/raw_controller.rb
+++ b/app/controllers/projects/raw_controller.rb
@@ -1,6 +1,7 @@
# Controller for viewing a file's raw
class Projects::RawController < Projects::ApplicationController
include ExtractsPath
+ include BlobHelper
before_action :require_non_empty_project
before_action :assign_ref_vars
@@ -12,12 +13,14 @@ class Projects::RawController < Projects::ApplicationController
if @blob
headers['X-Content-Type-Options'] = 'nosniff'
+ return if cached_blob?
+
if @blob.lfs_pointer?
send_lfs_object
else
headers.store(*Gitlab::Workhorse.send_git_blob(@repository, @blob))
headers['Content-Disposition'] = 'inline'
- headers['Content-Type'] = get_blob_type
+ headers['Content-Type'] = safe_content_type(@blob)
head :ok # 'render nothing: true' messes up the Content-Type
end
else
@@ -27,16 +30,6 @@ class Projects::RawController < Projects::ApplicationController
private
- def get_blob_type
- if @blob.text?
- 'text/plain; charset=utf-8'
- elsif @blob.image?
- @blob.content_type
- else
- 'application/octet-stream'
- end
- end
-
def send_lfs_object
lfs_object = find_lfs_object
diff --git a/app/controllers/projects/refs_controller.rb b/app/controllers/projects/refs_controller.rb
index a8f091819ca..00df1c9c965 100644
--- a/app/controllers/projects/refs_controller.rb
+++ b/app/controllers/projects/refs_controller.rb
@@ -64,9 +64,9 @@ class Projects::RefsController < Projects::ApplicationController
}
end
- if @logs.present?
- @log_url = namespace_project_tree_url(@project.namespace, @project, tree_join(@ref, @path || '/'))
- @more_log_url = logs_file_namespace_project_ref_path(@project.namespace, @project, @ref, @path || '', offset: (@offset + @limit))
+ offset = (@offset + @limit)
+ if contents.size > offset
+ @more_log_url = logs_file_namespace_project_ref_path(@project.namespace, @project, @ref, @path || '', offset: offset)
end
respond_to do |format|
diff --git a/app/controllers/projects/repositories_controller.rb b/app/controllers/projects/repositories_controller.rb
index ba9aea1c165..5c7614cfbaf 100644
--- a/app/controllers/projects/repositories_controller.rb
+++ b/app/controllers/projects/repositories_controller.rb
@@ -11,7 +11,9 @@ class Projects::RepositoriesController < Projects::ApplicationController
end
def archive
- render json: ArchiveRepositoryService.new(@project, params[:ref], params[:format]).execute
+ RepositoryArchiveCacheWorker.perform_async
+ headers.store(*Gitlab::Workhorse.send_git_archive(@project, params[:ref], params[:format]))
+ head :ok
rescue => ex
logger.error("#{self.class.name}: #{ex}")
return git_not_found!
diff --git a/app/controllers/projects/tags_controller.rb b/app/controllers/projects/tags_controller.rb
index 280fe12cc7c..e580487a2c6 100644
--- a/app/controllers/projects/tags_controller.rb
+++ b/app/controllers/projects/tags_controller.rb
@@ -34,6 +34,11 @@ class Projects::TagsController < Projects::ApplicationController
def destroy
DeleteTagService.new(project, current_user).execute(params[:id])
- redirect_to namespace_project_tags_path(@project.namespace, @project)
+ respond_to do |format|
+ format.html do
+ redirect_to namespace_project_tags_path(@project.namespace, @project)
+ end
+ format.js
+ end
end
end
diff --git a/app/controllers/projects_controller.rb b/app/controllers/projects_controller.rb
index 14ca7426c2f..aea08ecce3e 100644
--- a/app/controllers/projects_controller.rb
+++ b/app/controllers/projects_controller.rb
@@ -236,7 +236,7 @@ class ProjectsController < ApplicationController
Emoji.emojis.map do |name, emoji|
{
name: name,
- path: view_context.image_url("emoji/#{emoji["unicode"]}.png")
+ path: view_context.image_url("#{emoji["unicode"]}.png")
}
end
end
diff --git a/app/controllers/search_controller.rb b/app/controllers/search_controller.rb
index 9bb42ec86b3..e42d2d73947 100644
--- a/app/controllers/search_controller.rb
+++ b/app/controllers/search_controller.rb
@@ -1,4 +1,6 @@
class SearchController < ApplicationController
+ skip_before_action :authenticate_user!, :reject_blocked
+
include SearchHelper
layout 'search'
diff --git a/app/controllers/sessions_controller.rb b/app/controllers/sessions_controller.rb
index 44eb58e418b..65677a3dd3c 100644
--- a/app/controllers/sessions_controller.rb
+++ b/app/controllers/sessions_controller.rb
@@ -4,8 +4,10 @@ class SessionsController < Devise::SessionsController
skip_before_action :check_2fa_requirement, only: [:destroy]
+ prepend_before_action :check_initial_setup, only: [:new]
prepend_before_action :authenticate_with_two_factor, only: [:create]
prepend_before_action :store_redirect_path, only: [:new]
+
before_action :auto_sign_in_with_provider, only: [:new]
before_action :load_recaptcha
@@ -33,6 +35,22 @@ class SessionsController < Devise::SessionsController
private
+ # Handle an "initial setup" state, where there's only one user, it's an admin,
+ # and they require a password change.
+ def check_initial_setup
+ return unless User.count == 1
+
+ user = User.admins.last
+
+ return unless user && user.require_password?
+
+ token = user.generate_reset_token
+ user.save
+
+ redirect_to edit_user_password_path(reset_password_token: token),
+ notice: "Please create a password for your new account."
+ end
+
def user_params
params.require(:user).permit(:login, :password, :remember_me, :otp_attempt)
end
diff --git a/app/controllers/uploads_controller.rb b/app/controllers/uploads_controller.rb
index 868b05929d7..509f4f412ca 100644
--- a/app/controllers/uploads_controller.rb
+++ b/app/controllers/uploads_controller.rb
@@ -55,14 +55,15 @@ class UploadsController < ApplicationController
"user" => User,
"project" => Project,
"note" => Note,
- "group" => Group
+ "group" => Group,
+ "appearance" => Appearance
}
upload_models[params[:model]]
end
def upload_mount
- upload_mounts = %w(avatar attachment file)
+ upload_mounts = %w(avatar attachment file logo header_logo)
if upload_mounts.include?(params[:mounted_as])
params[:mounted_as]
diff --git a/app/controllers/users_controller.rb b/app/controllers/users_controller.rb
index 6055b606086..e10c633690f 100644
--- a/app/controllers/users_controller.rb
+++ b/app/controllers/users_controller.rb
@@ -3,13 +3,6 @@ class UsersController < ApplicationController
before_action :set_user
def show
- @contributed_projects = contributed_projects.joined(@user).reject(&:forked?)
-
- @projects = PersonalProjectsFinder.new(@user).execute(current_user)
- @projects = @projects.page(params[:page]).per(PER_PAGE)
-
- @groups = @user.groups.order_id_desc
-
respond_to do |format|
format.html
@@ -25,6 +18,45 @@ class UsersController < ApplicationController
end
end
+ def groups
+ load_groups
+
+ respond_to do |format|
+ format.html { render 'show' }
+ format.json do
+ render json: {
+ html: view_to_html_string("shared/groups/_list", groups: @groups)
+ }
+ end
+ end
+ end
+
+ def projects
+ load_projects
+
+ respond_to do |format|
+ format.html { render 'show' }
+ format.json do
+ render json: {
+ html: view_to_html_string("shared/projects/_list", projects: @projects, remote: true)
+ }
+ end
+ end
+ end
+
+ def contributed
+ load_contributed_projects
+
+ respond_to do |format|
+ format.html { render 'show' }
+ format.json do
+ render json: {
+ html: view_to_html_string("shared/projects/_list", projects: @contributed_projects)
+ }
+ end
+ end
+ end
+
def calendar
calendar = contributions_calendar
@timestamps = calendar.timestamps
@@ -35,12 +67,8 @@ class UsersController < ApplicationController
end
def calendar_activities
- @calendar_date = Date.parse(params[:date]) rescue nil
- @events = []
-
- if @calendar_date
- @events = contributions_calendar.events_by_date(@calendar_date)
- end
+ @calendar_date = Date.parse(params[:date]) rescue Date.today
+ @events = contributions_calendar.events_by_date(@calendar_date)
render 'calendar_activities', layout: false
end
@@ -57,7 +85,7 @@ class UsersController < ApplicationController
def contributions_calendar
@contributions_calendar ||= Gitlab::ContributionsCalendar.
- new(contributed_projects.reject(&:forked?), @user)
+ new(contributed_projects, @user)
end
def load_events
@@ -69,6 +97,20 @@ class UsersController < ApplicationController
limit_recent(20, params[:offset])
end
+ def load_projects
+ @projects =
+ PersonalProjectsFinder.new(@user).execute(current_user)
+ .page(params[:page]).per(PER_PAGE)
+ end
+
+ def load_contributed_projects
+ @contributed_projects = contributed_projects.joined(@user)
+ end
+
+ def load_groups
+ @groups = @user.groups.order_id_desc
+ end
+
def projects_for_current_user
ProjectsFinder.new.execute(current_user)
end
diff --git a/app/finders/issuable_finder.rb b/app/finders/issuable_finder.rb
index 0a4192e6bac..c88a420b412 100644
--- a/app/finders/issuable_finder.rb
+++ b/app/finders/issuable_finder.rb
@@ -119,6 +119,20 @@ class IssuableFinder
labels? && params[:label_name] == Label::None.title
end
+ def labels
+ return @labels if defined?(@labels)
+
+ if labels? && !filter_by_no_label?
+ @labels = Label.where(title: label_names)
+
+ if projects
+ @labels = @labels.where(project: projects)
+ end
+ else
+ @labels = Label.none
+ end
+ end
+
def assignee?
params[:assignee_id].present?
end
@@ -249,13 +263,9 @@ class IssuableFinder
def by_label(items)
if labels?
if filter_by_no_label?
- items = items.
- joins("LEFT OUTER JOIN label_links ON label_links.target_type = '#{klass.name}' AND label_links.target_id = #{klass.table_name}.id").
- where(label_links: { id: nil })
+ items = items.without_label
else
- label_names = params[:label_name].split(",")
-
- items = items.joins(:labels).where(labels: { title: label_names })
+ items = items.with_label(label_names)
if projects
items = items.where(labels: { project_id: projects })
@@ -266,6 +276,10 @@ class IssuableFinder
items
end
+ def label_names
+ params[:label_name].split(',')
+ end
+
def current_user_related?
params[:scope] == 'created-by-me' || params[:scope] == 'authored' || params[:scope] == 'assigned-to-me'
end
diff --git a/app/finders/todos_finder.rb b/app/finders/todos_finder.rb
new file mode 100644
index 00000000000..3ba27c40504
--- /dev/null
+++ b/app/finders/todos_finder.rb
@@ -0,0 +1,129 @@
+# TodosFinder
+#
+# Used to filter Todos by set of params
+#
+# Arguments:
+# current_user - which user use
+# params:
+# action_id: integer
+# author_id: integer
+# project_id; integer
+# state: 'pending' or 'done'
+# type: 'Issue' or 'MergeRequest'
+#
+
+class TodosFinder
+ NONE = '0'
+
+ attr_accessor :current_user, :params
+
+ def initialize(current_user, params)
+ @current_user = current_user
+ @params = params
+ end
+
+ def execute
+ items = current_user.todos
+ items = by_action_id(items)
+ items = by_author(items)
+ items = by_project(items)
+ items = by_state(items)
+ items = by_type(items)
+
+ items
+ end
+
+ private
+
+ def action_id?
+ action_id.present? && [Todo::ASSIGNED, Todo::MENTIONED].include?(action_id.to_i)
+ end
+
+ def action_id
+ params[:action_id]
+ end
+
+ def author?
+ params[:author_id].present?
+ end
+
+ def author
+ return @author if defined?(@author)
+
+ @author =
+ if author? && params[:author_id] != NONE
+ User.find(params[:author_id])
+ else
+ nil
+ end
+ end
+
+ def project?
+ params[:project_id].present?
+ end
+
+ def project
+ return @project if defined?(@project)
+
+ if project?
+ @project = Project.find(params[:project_id])
+
+ unless Ability.abilities.allowed?(current_user, :read_project, @project)
+ @project = nil
+ end
+ else
+ @project = nil
+ end
+
+ @project
+ end
+
+ def type?
+ type.present? && ['Issue', 'MergeRequest'].include?(type)
+ end
+
+ def type
+ params[:type]
+ end
+
+ def by_action_id(items)
+ if action_id?
+ items = items.where(action: action_id)
+ end
+
+ items
+ end
+
+ def by_author(items)
+ if author?
+ items = items.where(author_id: author.try(:id))
+ end
+
+ items
+ end
+
+ def by_project(items)
+ if project?
+ items = items.where(project: project)
+ end
+
+ items
+ end
+
+ def by_state(items)
+ case params[:state]
+ when 'done'
+ items.done
+ else
+ items.pending
+ end
+ end
+
+ def by_type(items)
+ if type?
+ items = items.where(target_type: type)
+ end
+
+ items
+ end
+end
diff --git a/app/helpers/appearances_helper.rb b/app/helpers/appearances_helper.rb
index c5820bf4c50..e0abc3a2869 100644
--- a/app/helpers/appearances_helper.rb
+++ b/app/helpers/appearances_helper.rb
@@ -1,21 +1,33 @@
module AppearancesHelper
- def brand_item
- nil
- end
-
def brand_title
- 'GitLab Community Edition'
+ if brand_item && brand_item.title
+ brand_item.title
+ else
+ 'GitLab Community Edition'
+ end
end
def brand_image
- nil
+ if brand_item.logo?
+ image_tag brand_item.logo
+ else
+ nil
+ end
end
def brand_text
- nil
+ markdown(brand_item.description)
+ end
+
+ def brand_item
+ @appearance ||= Appearance.first
end
def brand_header_logo
- render 'shared/logo.svg'
+ if brand_item && brand_item.header_logo?
+ image_tag brand_item.header_logo
+ else
+ render 'shared/logo.svg'
+ end
end
end
diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb
index 02357e2f23e..f0aa2b57121 100644
--- a/app/helpers/application_helper.rb
+++ b/app/helpers/application_helper.rb
@@ -118,12 +118,6 @@ module ApplicationHelper
grouped_options_for_select(options, @ref || @project.default_branch)
end
- def emoji_autocomplete_source
- # should be an array of strings
- # so to_s can be called, because it is sufficient and to_json is too slow
- Emoji.names.to_s
- end
-
# Define whenever show last push event
# with suggestion to create MR
def show_last_push_widget?(event)
@@ -280,76 +274,6 @@ module ApplicationHelper
end
end
- def issuable_link_next(project,issuable)
- if project.nil?
- nil
- elsif current_controller?(:issues)
- namespace_project_issue_path(project.namespace, project, next_issuable_for(project, issuable.id).try(:iid))
- elsif current_controller?(:merge_requests)
- namespace_project_merge_request_path(project.namespace, project, next_issuable_for(project, issuable.id).try(:iid))
- end
- end
-
- def issuable_link_prev(project,issuable)
- if project.nil?
- nil
- elsif current_controller?(:issues)
- namespace_project_issue_path(project.namespace, project, prev_issuable_for(project, issuable.id).try(:iid))
- elsif current_controller?(:merge_requests)
- namespace_project_merge_request_path(project.namespace, project, prev_issuable_for(project, issuable.id).try(:iid))
- end
- end
-
- def issuable_count(entity, project)
- if project.nil?
- 0
- elsif current_controller?(:issues)
- project.issues.send(entity).count
- elsif current_controller?(:merge_requests)
- project.merge_requests.send(entity).count
- end
- end
-
- def next_issuable_for(project, id)
- if project.nil?
- nil
- elsif current_controller?(:issues)
- project.issues.where("id > ?", id).last
- elsif current_controller?(:merge_requests)
- project.merge_requests.where("id > ?", id).last
- end
- end
-
- def has_next_issuable?(project, id)
- if project.nil?
- nil
- elsif current_controller?(:issues)
- project.issues.where("id > ?", id).last
- elsif current_controller?(:merge_requests)
- project.merge_requests.where("id > ?", id).last
- end
- end
-
- def prev_issuable_for(project, id)
- if project.nil?
- nil
- elsif current_controller?(:issues)
- project.issues.where("id < ?", id).first
- elsif current_controller?(:merge_requests)
- project.merge_requests.where("id < ?", id).first
- end
- end
-
- def has_prev_issuable?(project, id)
- if project.nil?
- nil
- elsif current_controller?(:issues)
- project.issues.where("id < ?", id).first
- elsif current_controller?(:merge_requests)
- project.merge_requests.where("id < ?", id).first
- end
- end
-
def state_filters_text_for(entity, project)
titles = {
opened: "Open"
diff --git a/app/helpers/auth_helper.rb b/app/helpers/auth_helper.rb
index de669e529a7..b4f80fd9b3e 100644
--- a/app/helpers/auth_helper.rb
+++ b/app/helpers/auth_helper.rb
@@ -6,6 +6,10 @@ module AuthHelper
Gitlab.config.ldap.enabled
end
+ def omniauth_enabled?
+ Gitlab.config.omniauth.enabled
+ end
+
def provider_has_icon?(name)
PROVIDERS_WITH_ICONS.include?(name.to_s)
end
diff --git a/app/helpers/blob_helper.rb b/app/helpers/blob_helper.rb
index 16967927922..0f77b3b299a 100644
--- a/app/helpers/blob_helper.rb
+++ b/app/helpers/blob_helper.rb
@@ -127,10 +127,6 @@ module BlobHelper
end
end
- def blob_svg?(blob)
- blob.language && blob.language.name == 'SVG'
- end
-
# SVGs can contain malicious JavaScript; only include whitelisted
# elements and attributes. Note that this whitelist is by no means complete
# and may omit some elements.
@@ -138,4 +134,43 @@ module BlobHelper
blob.data = Loofah.scrub_fragment(blob.data, :strip).to_xml
blob
end
+
+ # If we blindly set the 'real' content type when serving a Git blob we
+ # are enabling XSS attacks. An attacker could upload e.g. a Javascript
+ # file to a Git repository, trick the browser of a victim into
+ # downloading the blob, and then the 'application/javascript' content
+ # type would tell the browser to execute the attacker's Javascript. By
+ # overriding the content type and setting it to 'text/plain' (in the
+ # example of Javascript) we tell the browser of the victim not to
+ # execute untrusted data.
+ def safe_content_type(blob)
+ if blob.text?
+ 'text/plain; charset=utf-8'
+ elsif blob.image?
+ blob.content_type
+ else
+ 'application/octet-stream'
+ end
+ end
+
+ def cached_blob?
+ stale = stale?(etag: @blob.id) # The #stale? method sets cache headers.
+
+ # Because we are opionated we set the cache headers ourselves.
+ response.cache_control[:public] = @project.public?
+
+ if @ref && @commit && @ref == @commit.id
+ # This is a link to a commit by its commit SHA. That means that the blob
+ # is immutable. The only reason to invalidate the cache is if the commit
+ # was deleted or if the user lost access to the repository.
+ response.cache_control[:max_age] = Blob::CACHE_TIME_IMMUTABLE
+ else
+ # A branch or tag points at this blob. That means that the expected blob
+ # value may change over time.
+ response.cache_control[:max_age] = Blob::CACHE_TIME
+ end
+
+ response.etag = @blob.id
+ !stale
+ end
end
diff --git a/app/helpers/commits_helper.rb b/app/helpers/commits_helper.rb
index 1d14ee52cfc..a09e91578b6 100644
--- a/app/helpers/commits_helper.rb
+++ b/app/helpers/commits_helper.rb
@@ -123,6 +123,37 @@ module CommitsHelper
)
end
+ def revert_commit_link(commit, continue_to_path, btn_class: nil)
+ return unless current_user
+
+ tooltip = "Revert this #{revert_commit_type(commit)} in a new merge request"
+
+ if can_collaborate_with_project?
+ content_tag :span, 'data-toggle' => 'modal', 'data-target' => '#modal-revert-commit' do
+ link_to 'Revert', '#modal-revert-commit', 'data-toggle' => 'tooltip', 'data-container' => 'body', title: tooltip, class: "btn btn-default btn-grouped btn-#{btn_class}"
+ end
+ elsif can?(current_user, :fork_project, @project)
+ continue_params = {
+ to: continue_to_path,
+ notice: edit_in_new_fork_notice + ' Try to revert this commit again.',
+ notice_now: edit_in_new_fork_notice_now
+ }
+ fork_path = namespace_project_forks_path(@project.namespace, @project,
+ namespace_key: current_user.namespace.id,
+ continue: continue_params)
+
+ link_to 'Revert', fork_path, class: 'btn btn-grouped btn-close', method: :post, 'data-toggle' => 'tooltip', 'data-container' => 'body', title: tooltip
+ end
+ end
+
+ def revert_commit_type(commit)
+ if commit.merged_merge_request
+ 'merge request'
+ else
+ 'commit'
+ end
+ end
+
protected
# Private: Returns a link to a person. If the person has a matching user and
diff --git a/app/helpers/diff_helper.rb b/app/helpers/diff_helper.rb
index f9bacc8ba45..ff32e834499 100644
--- a/app/helpers/diff_helper.rb
+++ b/app/helpers/diff_helper.rb
@@ -12,40 +12,20 @@ module DiffHelper
params[:view] == 'parallel' ? 'parallel' : 'inline'
end
- def allowed_diff_size
- if diff_hard_limit_enabled?
- Commit::DIFF_HARD_LIMIT_FILES
- else
- Commit::DIFF_SAFE_FILES
- end
+ def diff_hard_limit_enabled?
+ params[:force_show_diff].present?
end
- def allowed_diff_lines
+ def diff_options
+ options = { ignore_whitespace_change: params[:w] == '1' }
if diff_hard_limit_enabled?
- Commit::DIFF_HARD_LIMIT_LINES
- else
- Commit::DIFF_SAFE_LINES
+ options.merge!(Commit.max_diff_options)
end
+ options
end
def safe_diff_files(diffs, diff_refs)
- lines = 0
- safe_files = []
- diffs.first(allowed_diff_size).each do |diff|
- lines += diff.diff.lines.count
- break if lines > allowed_diff_lines
- safe_files << Gitlab::Diff::File.new(diff, diff_refs)
- end
- safe_files
- end
-
- def diff_hard_limit_enabled?
- # Enabling hard limit allows user to see more diff information
- if params[:force_show_diff].present?
- true
- else
- false
- end
+ diffs.decorate! { |diff| Gitlab::Diff::File.new(diff, diff_refs) }
end
def generate_line_code(file_path, line)
@@ -69,7 +49,7 @@ module DiffHelper
end
def line_comments
- @line_comments ||= @line_notes.select(&:active?).group_by(&:line_code)
+ @line_comments ||= @line_notes.select(&:active?).sort_by(&:created_at).group_by(&:line_code)
end
def organize_comments(type_left, type_right, line_code_left, line_code_right)
@@ -137,7 +117,7 @@ module DiffHelper
# Always use HTML to handle case where JSON diff rendered this button
params_copy.delete(:format)
- link_to url_for(params_copy), id: "#{name}-diff-btn", class: (selected ? 'btn active' : 'btn') do
+ link_to url_for(params_copy), id: "#{name}-diff-btn", class: (selected ? 'btn active' : 'btn'), data: { view_type: name } do
title
end
end
diff --git a/app/helpers/events_helper.rb b/app/helpers/events_helper.rb
index 31bf45baeb7..e5fcaab9551 100644
--- a/app/helpers/events_helper.rb
+++ b/app/helpers/events_helper.rb
@@ -168,11 +168,11 @@ module EventsHelper
link_to(namespace_project_snippet_path(event.project.namespace,
event.project,
event.note_target)) do
- "#{event.note_target_type} ##{truncate event.note_target_id}"
+ "#{event.note_target_type} #{truncate event.note_target.to_reference}"
end
else
link_to event_note_target_path(event) do
- "#{event.note_target_type} ##{truncate event.note_target_iid}"
+ "#{event.note_target_type} #{truncate event.note_target.to_reference}"
end
end
else
diff --git a/app/helpers/gitlab_markdown_helper.rb b/app/helpers/gitlab_markdown_helper.rb
index 89d2a648494..2f760af02fd 100644
--- a/app/helpers/gitlab_markdown_helper.rb
+++ b/app/helpers/gitlab_markdown_helper.rb
@@ -50,6 +50,8 @@ module GitlabMarkdownHelper
context[:project] ||= @project
+ text = Banzai.pre_process(text, context)
+
html = Banzai.render(text, context)
context.merge!(
diff --git a/app/helpers/icons_helper.rb b/app/helpers/icons_helper.rb
index 84c6d0883b0..ab3ef454e1c 100644
--- a/app/helpers/icons_helper.rb
+++ b/app/helpers/icons_helper.rb
@@ -10,6 +10,15 @@ module IconsHelper
options.include?(:base) ? fa_stacked_icon(names, options) : fa_icon(names, options)
end
+ def audit_icon(names, options = {})
+ case names
+ when "standard"
+ names = "key"
+ end
+
+ options.include?(:base) ? fa_stacked_icon(names, options) : fa_icon(names, options)
+ end
+
def spinner(text = nil, visible = false)
css_class = 'loading'
css_class << ' hide' unless visible
@@ -37,7 +46,7 @@ module IconsHelper
else # Gitlab::VisibilityLevel::PUBLIC
'globe'
end
-
+
name << " fw" if fw
icon(name)
diff --git a/app/helpers/issuables_helper.rb b/app/helpers/issuables_helper.rb
new file mode 100644
index 00000000000..91a3aa371ef
--- /dev/null
+++ b/app/helpers/issuables_helper.rb
@@ -0,0 +1,37 @@
+module IssuablesHelper
+
+ def sidebar_gutter_toggle_icon
+ sidebar_gutter_collapsed? ? icon('angle-double-left') : icon('angle-double-right')
+ end
+
+ def sidebar_gutter_collapsed_class
+ "right-sidebar-#{sidebar_gutter_collapsed? ? 'collapsed' : 'expanded'}"
+ end
+
+ def issuables_count(issuable)
+ base_issuable_scope(issuable).maximum(:iid)
+ end
+
+ def next_issuable_for(issuable)
+ base_issuable_scope(issuable).where('iid > ?', issuable.iid).last
+ end
+
+ def prev_issuable_for(issuable)
+ base_issuable_scope(issuable).where('iid < ?', issuable.iid).first
+ end
+
+ private
+
+ def sidebar_gutter_collapsed?
+ cookies[:collapsed_gutter] == 'true'
+ end
+
+ def base_issuable_scope(issuable)
+ issuable.project.send(issuable.class.table_name).send(issuable_state_scope(issuable))
+ end
+
+ def issuable_state_scope(issuable)
+ issuable.open? ? :opened : :closed
+ end
+
+end
diff --git a/app/helpers/labels_helper.rb b/app/helpers/labels_helper.rb
index 1c7fcc13b42..89a054289e8 100644
--- a/app/helpers/labels_helper.rb
+++ b/app/helpers/labels_helper.rb
@@ -50,19 +50,25 @@ module LabelsHelper
@project.labels.pluck(:title)
end
- def render_colored_label(label)
+ def render_colored_label(label, label_suffix = '')
label_color = label.color || Label::DEFAULT_COLOR
text_color = text_color_for_bg(label_color)
# Intentionally not using content_tag here so that this method can be called
# by LabelReferenceFilter
span = %(<span class="label color-label") +
- %( style="background-color: #{label_color}; color: #{text_color}">) +
- escape_once(label.name) + '</span>'
+ %(style="background-color: #{label_color}; color: #{text_color}">) +
+ %(#{escape_once(label.name)}#{label_suffix}</span>)
span.html_safe
end
+ def render_colored_cross_project_label(label)
+ label_suffix = label.project.name_with_namespace
+ label_suffix = " <i>in #{escape_once(label_suffix)}</i>"
+ render_colored_label(label, label_suffix)
+ end
+
def suggested_colors
[
'#0033CC',
@@ -119,5 +125,6 @@ module LabelsHelper
end
# Required for Banzai::Filter::LabelReferenceFilter
- module_function :render_colored_label, :text_color_for_bg, :escape_once
+ module_function :render_colored_label, :render_colored_cross_project_label,
+ :text_color_for_bg, :escape_once
end
diff --git a/app/helpers/milestones_helper.rb b/app/helpers/milestones_helper.rb
index a42cbcff182..e3e7daa49c5 100644
--- a/app/helpers/milestones_helper.rb
+++ b/app/helpers/milestones_helper.rb
@@ -9,6 +9,32 @@ module MilestonesHelper
end
end
+ def milestones_label_path(opts = {})
+ if @project
+ namespace_project_issues_path(@project.namespace, @project, opts)
+ elsif @group
+ issues_group_path(@group, opts)
+ else
+ issues_dashboard_path(opts)
+ end
+ end
+
+ def milestones_browse_issuables_path(milestone, type:)
+ opts = { milestone_title: milestone.title }
+
+ if @project
+ polymorphic_path([@project.namespace.becomes(Namespace), @project, type], opts)
+ elsif @group
+ polymorphic_url([type, @group], opts)
+ else
+ polymorphic_url([type, :dashboard], opts)
+ end
+ end
+
+ def milestone_issues_by_label_count(milestone, label, state:)
+ milestone.issues.with_label(label.title).send(state).size
+ end
+
def milestone_progress_bar(milestone)
options = {
class: 'progress-bar progress-bar-success',
@@ -36,4 +62,14 @@ module MilestonesHelper
options_from_collection_for_select(grouped_milestones, 'name', 'title', params[:milestone_title])
end
+
+ def milestone_remaining_days(milestone)
+ if milestone.expired?
+ content_tag(:strong, 'expired')
+ elsif milestone.due_date
+ days = milestone.remaining_days
+ content = content_tag(:strong, days)
+ content << " #{'day'.pluralize(days)} remaining"
+ end
+ end
end
diff --git a/app/helpers/nav_helper.rb b/app/helpers/nav_helper.rb
index 75f2ed5e054..5d86bd490a8 100644
--- a/app/helpers/nav_helper.rb
+++ b/app/helpers/nav_helper.rb
@@ -3,18 +3,6 @@ module NavHelper
cookies[:collapsed_nav] == 'true'
end
- def sidebar_gutter_collapsed_class
- if cookies[:collapsed_gutter] == 'true'
- "right-sidebar-collapsed"
- else
- "right-sidebar-expanded"
- end
- end
-
- def sidebar_gutter_collapsed?
- cookies[:collapsed_gutter] == 'true'
- end
-
def nav_sidebar_class
if nav_menu_collapsed?
"sidebar-collapsed"
@@ -32,9 +20,10 @@ module NavHelper
end
def page_gutter_class
- if current_path?('merge_requests#show') ||
- current_path?('merge_requests#diffs') ||
- current_path?('merge_requests#commits') ||
+ if current_path?('merge_requests#show') ||
+ current_path?('merge_requests#diffs') ||
+ current_path?('merge_requests#commits') ||
+ current_path?('merge_requests#builds') ||
current_path?('issues#show')
if cookies[:collapsed_gutter] == 'true'
"page-gutter right-sidebar-collapsed"
diff --git a/app/helpers/projects_helper.rb b/app/helpers/projects_helper.rb
index d6fb629b0c2..c8061fcdc59 100644
--- a/app/helpers/projects_helper.rb
+++ b/app/helpers/projects_helper.rb
@@ -38,12 +38,16 @@ module ProjectsHelper
author_html << image_tag(avatar_icon(author, opts[:size]), width: opts[:size], class: "avatar avatar-inline #{"s#{opts[:size]}" if opts[:size]}", alt:'') if opts[:avatar]
# Build name span tag
- author_html << content_tag(:span, sanitize(author.name), class: opts[:author_class]) if opts[:name]
+ if opts[:by_username]
+ author_html << content_tag(:span, sanitize("@#{author.username}"), class: opts[:author_class]) if opts[:name]
+ else
+ author_html << content_tag(:span, sanitize(author.name), class: opts[:author_class]) if opts[:name]
+ end
author_html = author_html.html_safe
if opts[:name]
- link_to(author_html, user_path(author), class: "author_link").html_safe
+ link_to(author_html, user_path(author), class: "author_link #{"#{opts[:mobile_classes]}" if opts[:mobile_classes]}").html_safe
else
title = opts[:title].sub(":name", sanitize(author.name))
link_to(author_html, user_path(author), class: "author_link has_tooltip", data: { 'original-title'.to_sym => title, container: 'body' } ).html_safe
diff --git a/app/helpers/sorting_helper.rb b/app/helpers/sorting_helper.rb
index 241179b0212..f9026b887da 100644
--- a/app/helpers/sorting_helper.rb
+++ b/app/helpers/sorting_helper.rb
@@ -11,6 +11,8 @@ module SortingHelper
sort_value_largest_repo => sort_title_largest_repo,
sort_value_recently_signin => sort_title_recently_signin,
sort_value_oldest_signin => sort_title_oldest_signin,
+ sort_value_downvotes => sort_title_downvotes,
+ sort_value_upvotes => sort_title_upvotes
}
end
@@ -54,6 +56,14 @@ module SortingHelper
'Oldest sign in'
end
+ def sort_title_downvotes
+ 'Least popular'
+ end
+
+ def sort_title_upvotes
+ 'Most popular'
+ end
+
def sort_value_oldest_updated
'updated_asc'
end
@@ -93,4 +103,12 @@ module SortingHelper
def sort_value_oldest_signin
'oldest_sign_in'
end
+
+ def sort_value_downvotes
+ 'downvotes_desc'
+ end
+
+ def sort_value_upvotes
+ 'upvotes_desc'
+ end
end
diff --git a/app/helpers/todos_helper.rb b/app/helpers/todos_helper.rb
new file mode 100644
index 00000000000..4b745a5b969
--- /dev/null
+++ b/app/helpers/todos_helper.rb
@@ -0,0 +1,87 @@
+module TodosHelper
+ def todos_pending_count
+ current_user.todos.pending.count
+ end
+
+ def todos_done_count
+ current_user.todos.done.count
+ end
+
+ def todo_action_name(todo)
+ case todo.action
+ when Todo::ASSIGNED then 'assigned you'
+ when Todo::MENTIONED then 'mentioned you on'
+ end
+ end
+
+ def todo_target_link(todo)
+ target = todo.target_type.titleize.downcase
+ link_to "#{target} #{todo.target.to_reference}", todo_target_path(todo)
+ end
+
+ def todo_target_path(todo)
+ anchor = dom_id(todo.note) if todo.note.present?
+
+ polymorphic_path([todo.project.namespace.becomes(Namespace),
+ todo.project, todo.target], anchor: anchor)
+ end
+
+ def todos_filter_params
+ {
+ state: params[:state],
+ project_id: params[:project_id],
+ author_id: params[:author_id],
+ type: params[:type],
+ action_id: params[:action_id],
+ }
+ end
+
+ def todos_filter_path(options = {})
+ without = options.delete(:without)
+
+ options = todos_filter_params.merge(options)
+
+ if without.present?
+ without.each do |key|
+ options.delete(key)
+ end
+ end
+
+ path = request.path
+ path << "?#{options.to_param}"
+ path
+ end
+
+ def todo_actions_options
+ actions = [
+ OpenStruct.new(id: '', title: 'Any Action'),
+ OpenStruct.new(id: Todo::ASSIGNED, title: 'Assigned'),
+ OpenStruct.new(id: Todo::MENTIONED, title: 'Mentioned')
+ ]
+
+ options_from_collection_for_select(actions, 'id', 'title', params[:action_id])
+ end
+
+ def todo_projects_options
+ projects = current_user.authorized_projects.sorted_by_activity.non_archived
+ projects = projects.includes(:namespace)
+
+ projects = projects.map do |project|
+ OpenStruct.new(id: project.id, title: project.name_with_namespace)
+ end
+
+ projects.unshift(OpenStruct.new(id: '', title: 'Any Project'))
+
+ options_from_collection_for_select(projects, 'id', 'title', params[:project_id])
+ end
+
+ def todo_types_options
+ types = [
+ OpenStruct.new(title: 'Any Type', name: ''),
+ OpenStruct.new(title: 'Issue', name: 'Issue'),
+ OpenStruct.new(title: 'Merge Request', name: 'MergeRequest')
+ ]
+
+ options_from_collection_for_select(types, 'name', 'title', params[:type])
+ end
+end
diff --git a/app/helpers/tree_helper.rb b/app/helpers/tree_helper.rb
index 2ad7c80dae0..4920ca5af6e 100644
--- a/app/helpers/tree_helper.rb
+++ b/app/helpers/tree_helper.rb
@@ -56,8 +56,7 @@ module TreeHelper
return false unless on_top_of_branch?(project, ref)
- can?(current_user, :push_code, project) ||
- (current_user && current_user.already_forked?(project))
+ can_collaborate_with_project?(project)
end
def tree_edit_branch(project = @project, ref = @ref)
diff --git a/app/models/ability.rb b/app/models/ability.rb
index a866eadeebb..f34554d557c 100644
--- a/app/models/ability.rb
+++ b/app/models/ability.rb
@@ -188,6 +188,7 @@ class Ability
def project_dev_rules
@project_dev_rules ||= project_report_rules + [
:admin_merge_request,
+ :update_merge_request,
:create_commit_status,
:update_commit_status,
:create_build,
@@ -212,7 +213,6 @@ class Ability
@project_master_rules ||= project_dev_rules + [
:push_code_to_protected_branches,
:update_project_snippet,
- :update_merge_request,
:admin_milestone,
:admin_project_snippet,
:admin_project_member,
diff --git a/app/models/appearance.rb b/app/models/appearance.rb
new file mode 100644
index 00000000000..4cf8dd9a8ce
--- /dev/null
+++ b/app/models/appearance.rb
@@ -0,0 +1,9 @@
+class Appearance < ActiveRecord::Base
+ validates :title, presence: true
+ validates :description, presence: true
+ validates :logo, file_size: { maximum: 1.megabyte }
+ validates :header_logo, file_size: { maximum: 1.megabyte }
+
+ mount_uploader :logo, AttachmentUploader
+ mount_uploader :header_logo, AttachmentUploader
+end
diff --git a/app/models/blob.rb b/app/models/blob.rb
new file mode 100644
index 00000000000..72e6c5fa3fd
--- /dev/null
+++ b/app/models/blob.rb
@@ -0,0 +1,37 @@
+# Blob is a Rails-specific wrapper around Gitlab::Git::Blob objects
+class Blob < SimpleDelegator
+ CACHE_TIME = 60 # Cache raw blobs referred to by a (mutable) ref for 1 minute
+ CACHE_TIME_IMMUTABLE = 3600 # Cache blobs referred to by an immutable reference for 1 hour
+
+ # Wrap a Gitlab::Git::Blob object, or return nil when given nil
+ #
+ # This method prevents the decorated object from evaluating to "truthy" when
+ # given a nil value. For example:
+ #
+ # blob = Blob.new(nil)
+ # puts "truthy" if blob # => "truthy"
+ #
+ # blob = Blob.decorate(nil)
+ # puts "truthy" if blob # No output
+ def self.decorate(blob)
+ return if blob.nil?
+
+ new(blob)
+ end
+
+ def svg?
+ text? && language && language.name == 'SVG'
+ end
+
+ def to_partial_path
+ if lfs_pointer?
+ 'download'
+ elsif image? || svg?
+ 'image'
+ elsif text?
+ 'text'
+ else
+ 'download'
+ end
+ end
+end
diff --git a/app/models/ci/build.rb b/app/models/ci/build.rb
index 623edd8bc57..1227458e525 100644
--- a/app/models/ci/build.rb
+++ b/app/models/ci/build.rb
@@ -31,15 +31,19 @@
# artifacts_file :text
# gl_project_id :integer
# artifacts_metadata :text
+# erased_by_id :integer
+# erased_at :datetime
#
module Ci
class Build < CommitStatus
include Gitlab::Application.routes.url_helpers
+
LAZY_ATTRIBUTES = ['trace']
belongs_to :runner, class_name: 'Ci::Runner'
belongs_to :trigger_request, class_name: 'Ci::TriggerRequest'
+ belongs_to :erased_by, class_name: 'User'
serialize :options
@@ -103,23 +107,22 @@ module Ci
end
state_machine :status, initial: :pending do
- after_transition pending: :running do |build, transition|
+ after_transition pending: :running do |build|
build.execute_hooks
end
- after_transition any => [:success, :failed, :canceled] do |build, transition|
- return unless build.project
+ # We use around_transition to create builds for next stage as soon as possible, before the `after_*` is executed
+ around_transition any => [:success, :failed, :canceled] do |build, block|
+ block.call
+ build.commit.create_next_builds(build) if build.commit
+ end
+ after_transition any => [:success, :failed, :canceled] do |build|
build.update_coverage
- build.commit.create_next_builds(build)
build.execute_hooks
end
end
- def ignored?
- failed? && allow_failure?
- end
-
def retryable?
project.builds_enabled? && commands.present?
end
@@ -179,6 +182,7 @@ module Ci
end
def update_coverage
+ return unless project
coverage_regex = project.build_coverage_regex
return unless coverage_regex
coverage = extract_coverage(trace, coverage_regex)
@@ -203,6 +207,10 @@ module Ci
end
end
+ def has_trace?
+ raw_trace.present?
+ end
+
def raw_trace
if File.file?(path_to_trace)
File.read(path_to_trace)
@@ -330,6 +338,7 @@ module Ci
end
def execute_hooks
+ return unless project
build_data = Gitlab::BuildDataBuilder.build(self)
project.execute_hooks(build_data.dup, :build_hooks)
project.execute_services(build_data.dup, :build_hooks)
@@ -359,6 +368,33 @@ module Ci
Gitlab::Ci::Build::Artifacts::Metadata.new(artifacts_metadata.path, path, **options).to_entry
end
+ def erase(opts = {})
+ return false unless erasable?
+
+ remove_artifacts_file!
+ remove_artifacts_metadata!
+ erase_trace!
+ update_erased!(opts[:erased_by])
+ end
+
+ def erasable?
+ complete? && (artifacts? || has_trace?)
+ end
+
+ def erased?
+ !self.erased_at.nil?
+ end
+
+ private
+
+ def erase_trace!
+ self.trace = nil
+ end
+
+ def update_erased!(user = nil)
+ self.update(erased_by: user, erased_at: Time.now)
+ end
+
private
def yaml_variables
diff --git a/app/models/ci/runner.rb b/app/models/ci/runner.rb
index 38b20cd7faa..e725a6d468c 100644
--- a/app/models/ci/runner.rb
+++ b/app/models/ci/runner.rb
@@ -22,6 +22,7 @@ module Ci
extend Ci::Model
LAST_CONTACT_TIME = 5.minutes.ago
+ AVAILABLE_SCOPES = ['specific', 'shared', 'active', 'paused', 'online']
has_many :builds, class_name: 'Ci::Build'
has_many :runner_projects, dependent: :destroy, class_name: 'Ci::RunnerProject'
@@ -38,6 +39,11 @@ module Ci
scope :online, ->() { where('contacted_at > ?', LAST_CONTACT_TIME) }
scope :ordered, ->() { order(id: :desc) }
+ scope :owned_or_shared, ->(project_id) do
+ joins('LEFT JOIN ci_runner_projects ON ci_runner_projects.runner_id = ci_runners.id')
+ .where("ci_runner_projects.gl_project_id = :project_id OR ci_runners.is_shared = true", project_id: project_id)
+ end
+
acts_as_taggable
def self.search(query)
diff --git a/app/models/commit.rb b/app/models/commit.rb
index 23b771aebb7..ce0b85d50cf 100644
--- a/app/models/commit.rb
+++ b/app/models/commit.rb
@@ -12,12 +12,7 @@ class Commit
attr_accessor :project
- # Safe amount of changes (files and lines) in one commit to render
- # Used to prevent 500 error on huge commits by suppressing diff
- #
- # User can force display of diff above this size
- DIFF_SAFE_FILES = 100 unless defined?(DIFF_SAFE_FILES)
- DIFF_SAFE_LINES = 5000 unless defined?(DIFF_SAFE_LINES)
+ DIFF_SAFE_LINES = Gitlab::Git::DiffCollection::DEFAULT_LIMITS[:max_lines]
# Commits above this size will not be rendered in HTML
DIFF_HARD_LIMIT_FILES = 1000 unless defined?(DIFF_HARD_LIMIT_FILES)
@@ -36,13 +31,20 @@ class Commit
# Calculate number of lines to render for diffs
def diff_line_count(diffs)
- diffs.reduce(0) { |sum, d| sum + d.diff.lines.count }
+ diffs.reduce(0) { |sum, d| sum + Gitlab::Git::Util.count_lines(d.diff) }
end
# Truncate sha to 8 characters
def truncate_sha(sha)
sha[0..7]
end
+
+ def max_diff_options
+ {
+ max_files: DIFF_HARD_LIMIT_FILES,
+ max_lines: DIFF_HARD_LIMIT_LINES,
+ }
+ end
end
attr_accessor :raw
@@ -215,6 +217,44 @@ class Commit
ci_commit.try(:status) || :not_found
end
+ def revert_branch_name
+ "revert-#{short_id}"
+ end
+
+ def revert_description
+ if merged_merge_request
+ "This reverts merge request #{merged_merge_request.to_reference}"
+ else
+ "This reverts commit #{sha}"
+ end
+ end
+
+ def revert_message
+ %Q{Revert "#{title}"\n\n#{revert_description}}
+ end
+
+ def reverts_commit?(commit)
+ description? && description.include?(commit.revert_description)
+ end
+
+ def merge_commit?
+ parents.size > 1
+ end
+
+ def merged_merge_request
+ return @merged_merge_request if defined?(@merged_merge_request)
+
+ @merged_merge_request = project.merge_requests.find_by(merge_commit_sha: id) if merge_commit?
+ end
+
+ def has_been_reverted?(current_user = nil, noteable = self)
+ Gitlab::ReferenceExtractor.lazily do
+ noteable.notes.system.flat_map do |note|
+ note.all_references(current_user).commits
+ end
+ end.any? { |commit_ref| commit_ref.reverts_commit?(self) }
+ end
+
private
def repo_changes
diff --git a/app/models/commit_status.rb b/app/models/commit_status.rb
index 66e0502fc0c..7ef50836322 100644
--- a/app/models/commit_status.rb
+++ b/app/models/commit_status.rb
@@ -75,16 +75,16 @@ class CommitStatus < ActiveRecord::Base
transition [:pending, :running] => :canceled
end
- after_transition pending: :running do |build, transition|
- build.update_attributes started_at: Time.now
+ after_transition pending: :running do |commit_status|
+ commit_status.update_attributes started_at: Time.now
end
- after_transition any => [:success, :failed, :canceled] do |build, transition|
- build.update_attributes finished_at: Time.now
+ after_transition any => [:success, :failed, :canceled] do |commit_status|
+ commit_status.update_attributes finished_at: Time.now
end
- after_transition [:pending, :running] => :success do |build, transition|
- MergeRequests::MergeWhenBuildSucceedsService.new(build.commit.project, nil).trigger(build)
+ after_transition [:pending, :running] => :success do |commit_status|
+ MergeRequests::MergeWhenBuildSucceedsService.new(commit_status.commit.project, nil).trigger(commit_status)
end
state :pending, value: 'pending'
@@ -113,6 +113,10 @@ class CommitStatus < ActiveRecord::Base
canceled? || success? || failed?
end
+ def ignored?
+ failed? && allow_failure?
+ end
+
def duration
if started_at && finished_at
finished_at - started_at
diff --git a/app/models/concerns/issuable.rb b/app/models/concerns/issuable.rb
index cf6aa592e2a..27b97944e38 100644
--- a/app/models/concerns/issuable.rb
+++ b/app/models/concerns/issuable.rb
@@ -29,15 +29,19 @@ module Issuable
scope :assigned, -> { where("assignee_id IS NOT NULL") }
scope :unassigned, -> { where("assignee_id IS NULL") }
scope :of_projects, ->(ids) { where(project_id: ids) }
+ scope :of_milestones, ->(ids) { where(milestone_id: ids) }
scope :opened, -> { with_state(:opened, :reopened) }
scope :only_opened, -> { with_state(:opened) }
scope :only_reopened, -> { with_state(:reopened) }
scope :closed, -> { with_state(:closed) }
scope :order_milestone_due_desc, -> { joins(:milestone).reorder('milestones.due_date DESC, milestones.id DESC') }
scope :order_milestone_due_asc, -> { joins(:milestone).reorder('milestones.due_date ASC, milestones.id ASC') }
+ scope :with_label, ->(title) { joins(:labels).where(labels: { title: title }) }
+ scope :without_label, -> { joins("LEFT OUTER JOIN label_links ON label_links.target_type = '#{name}' AND label_links.target_id = #{table_name}.id").where(label_links: { id: nil }) }
scope :join_project, -> { joins(:project) }
scope :references_project, -> { references(:project) }
+ scope :non_archived, -> { join_project.merge(Project.non_archived) }
delegate :name,
:email,
@@ -69,10 +73,35 @@ module Issuable
case method.to_s
when 'milestone_due_asc' then order_milestone_due_asc
when 'milestone_due_desc' then order_milestone_due_desc
+ when 'downvotes_desc' then order_downvotes_desc
+ when 'upvotes_desc' then order_upvotes_desc
else
order_by(method)
end
end
+
+ def order_downvotes_desc
+ order_votes_desc('thumbsdown')
+ end
+
+ def order_upvotes_desc
+ order_votes_desc('thumbsup')
+ end
+
+ def order_votes_desc(award_emoji_name)
+ issuable_table = self.arel_table
+ note_table = Note.arel_table
+
+ join_clause = issuable_table.join(note_table, Arel::Nodes::OuterJoin).on(
+ note_table[:noteable_id].eq(issuable_table[:id]).and(
+ note_table[:noteable_type].eq(self.name).and(
+ note_table[:is_award].eq(true).and(note_table[:note].eq(award_emoji_name))
+ )
+ )
+ ).join_sources
+
+ joins(join_clause).group(issuable_table[:id]).reorder("COUNT(notes.id) DESC")
+ end
end
def today?
@@ -129,13 +158,10 @@ module Issuable
hook_data = {
object_kind: self.class.name.underscore,
user: user.hook_attrs,
- repository: {
- name: project.name,
- url: project.url_to_repo,
- description: project.description,
- homepage: project.web_url
- },
- object_attributes: hook_attrs
+ project: project.hook_attrs,
+ object_attributes: hook_attrs,
+ # DEPRECATED
+ repository: project.hook_attrs.slice(:name, :url, :description, :homepage)
}
hook_data.merge!(assignee: assignee.hook_attrs) if assignee
diff --git a/app/models/concerns/milestoneish.rb b/app/models/concerns/milestoneish.rb
new file mode 100644
index 00000000000..d67df7c1d9c
--- /dev/null
+++ b/app/models/concerns/milestoneish.rb
@@ -0,0 +1,25 @@
+module Milestoneish
+ def closed_items_count
+ issues.closed.size + merge_requests.closed_and_merged.size
+ end
+
+ def total_items_count
+ issues.size + merge_requests.size
+ end
+
+ def complete?
+ total_items_count == closed_items_count
+ end
+
+ def percent_complete
+ ((closed_items_count * 100) / total_items_count).abs
+ rescue ZeroDivisionError
+ 0
+ end
+
+ def remaining_days
+ return 0 if !due_date || expired?
+
+ (due_date - Date.today).to_i
+ end
+end
diff --git a/app/models/diff_line.rb b/app/models/diff_line.rb
deleted file mode 100644
index ad37945874a..00000000000
--- a/app/models/diff_line.rb
+++ /dev/null
@@ -1,3 +0,0 @@
-class DiffLine
- attr_accessor :type, :content, :num, :code
-end
diff --git a/app/models/global_label.rb b/app/models/global_label.rb
index 0171f7d54b7..ddd4bad5c21 100644
--- a/app/models/global_label.rb
+++ b/app/models/global_label.rb
@@ -2,16 +2,19 @@ class GlobalLabel
attr_accessor :title, :labels
alias_attribute :name, :title
+ delegate :color, :description, to: :@first_label
+
def self.build_collection(labels)
labels = labels.group_by(&:title)
- labels.map do |title, label|
- new(title, label)
+ labels.map do |title, labels|
+ new(title, labels)
end
end
def initialize(title, labels)
@title = title
@labels = labels
+ @first_label = labels.find { |lbl| lbl.description.present? } || labels.first
end
end
diff --git a/app/models/global_milestone.rb b/app/models/global_milestone.rb
index 7ee276255a0..97bd79af083 100644
--- a/app/models/global_milestone.rb
+++ b/app/models/global_milestone.rb
@@ -1,4 +1,6 @@
class GlobalMilestone
+ include Milestoneish
+
attr_accessor :title, :milestones
alias_attribute :name, :title
@@ -28,33 +30,7 @@ class GlobalMilestone
end
def projects
- milestones.map { |milestone| milestone.project }
- end
-
- def issue_count
- milestones.map { |milestone| milestone.issues.count }.sum
- end
-
- def merge_requests_count
- milestones.map { |milestone| milestone.merge_requests.count }.sum
- end
-
- def open_items_count
- milestones.map { |milestone| milestone.open_items_count }.sum
- end
-
- def closed_items_count
- milestones.map { |milestone| milestone.closed_items_count }.sum
- end
-
- def total_items_count
- milestones.map { |milestone| milestone.total_items_count }.sum
- end
-
- def percent_complete
- ((closed_items_count * 100) / total_items_count).abs
- rescue ZeroDivisionError
- 0
+ @projects ||= Project.for_milestones(milestones.map(&:id))
end
def state
@@ -76,35 +52,20 @@ class GlobalMilestone
end
def issues
- @issues ||= milestones.map(&:issues).flatten.group_by(&:state)
+ @issues ||= Issue.of_milestones(milestones.map(&:id)).includes(:project)
end
def merge_requests
- @merge_requests ||= milestones.map(&:merge_requests).flatten.group_by(&:state)
+ @merge_requests ||= MergeRequest.of_milestones(milestones.map(&:id)).includes(:target_project)
end
def participants
@participants ||= milestones.map(&:participants).flatten.compact.uniq
end
- def opened_issues
- issues.values_at("opened", "reopened").compact.flatten
- end
-
- def closed_issues
- issues['closed']
- end
-
- def opened_merge_requests
- merge_requests.values_at("opened", "reopened").compact.flatten
- end
-
- def closed_merge_requests
- merge_requests.values_at("closed", "merged", "locked").compact.flatten
- end
-
- def complete?
- total_items_count == closed_items_count
+ def labels
+ @labels ||= GlobalLabel.build_collection(milestones.map(&:labels).flatten)
+ .sort_by!(&:title)
end
def due_date
diff --git a/app/models/label.rb b/app/models/label.rb
index 220da10a6ab..5ff644b8426 100644
--- a/app/models/label.rb
+++ b/app/models/label.rb
@@ -2,13 +2,14 @@
#
# Table name: labels
#
-# id :integer not null, primary key
-# title :string(255)
-# color :string(255)
-# project_id :integer
-# created_at :datetime
-# updated_at :datetime
-# template :boolean default(FALSE)
+# id :integer not null, primary key
+# title :string(255)
+# color :string(255)
+# project_id :integer
+# created_at :datetime
+# updated_at :datetime
+# template :boolean default(FALSE)
+# description :string(255)
#
class Label < ActiveRecord::Base
@@ -26,6 +27,7 @@ class Label < ActiveRecord::Base
belongs_to :project
has_many :label_links, dependent: :destroy
has_many :issues, through: :label_links, source: :target, source_type: 'Issue'
+ has_many :merge_requests, through: :label_links, source: :target, source_type: 'MergeRequest'
validates :color, color: true, allow_blank: false
validates :project, presence: true, unless: Proc.new { |service| service.template? }
@@ -46,10 +48,15 @@ class Label < ActiveRecord::Base
'~'
end
+ ##
# Pattern used to extract label references from text
+ #
+ # This pattern supports cross-project references.
+ #
def self.reference_pattern
%r{
- #{reference_prefix}
+ (#{Project.reference_pattern})?
+ #{Regexp.escape(reference_prefix)}
(?:
(?<label_id>\d+) | # Integer-based label ID, or
(?<label_name>
@@ -60,24 +67,31 @@ class Label < ActiveRecord::Base
}x
end
+ def self.link_reference_pattern
+ nil
+ end
+
+ ##
# Returns the String necessary to reference this Label in Markdown
#
# format - Symbol format to use (default: :id, optional: :name)
#
- # Note that its argument differs from other objects implementing Referable. If
- # a non-Symbol argument is given (such as a Project), it will default to :id.
- #
# Examples:
#
- # Label.first.to_reference # => "~1"
- # Label.first.to_reference(:name) # => "~\"bug\""
+ # Label.first.to_reference # => "~1"
+ # Label.first.to_reference(format: :name) # => "~\"bug\""
+ # Label.first.to_reference(project) # => "gitlab-org/gitlab-ce~1"
#
# Returns a String
- def to_reference(format = :id)
- if format == :name && !name.include?('"')
- %(#{self.class.reference_prefix}"#{name}")
+ #
+ def to_reference(from_project = nil, format: :id)
+ format_reference = label_format_reference(format)
+ reference = "#{self.class.reference_prefix}#{format_reference}"
+
+ if cross_project_reference?(from_project)
+ project.to_reference + reference
else
- "#{self.class.reference_prefix}#{id}"
+ reference
end
end
@@ -85,7 +99,27 @@ class Label < ActiveRecord::Base
issues.opened.count
end
+ def closed_issues_count
+ issues.closed.count
+ end
+
+ def open_merge_requests_count
+ merge_requests.opened.count
+ end
+
def template?
template
end
+
+ private
+
+ def label_format_reference(format = :id)
+ raise StandardError, 'Unknown format' unless [:id, :name].include?(format)
+
+ if format == :name && !name.include?('"')
+ %("#{name}")
+ else
+ id
+ end
+ end
end
diff --git a/app/models/merge_request.rb b/app/models/merge_request.rb
index 1be8061e53d..c1e18bb3cc5 100644
--- a/app/models/merge_request.rb
+++ b/app/models/merge_request.rb
@@ -24,6 +24,7 @@
# merge_params :text
# merge_when_build_succeeds :boolean default(FALSE), not null
# merge_user_id :integer
+# merge_commit_sha :string
#
require Rails.root.join("app/models/commit")
@@ -47,7 +48,7 @@ class MergeRequest < ActiveRecord::Base
after_create :create_merge_request_diff
after_update :update_merge_request_diff
- delegate :commits, :diffs, :diffs_no_whitespace, to: :merge_request_diff, prefix: nil
+ delegate :commits, :diffs, :real_size, to: :merge_request_diff, prefix: nil
# When this attribute is true some MR validation is ignored
# It allows us to close or modify broken merge requests
@@ -55,8 +56,7 @@ class MergeRequest < ActiveRecord::Base
# Temporary fields to store compare vars
# when creating new merge request
- attr_accessor :can_be_created, :compare_failed,
- :compare_commits, :compare_diffs
+ attr_accessor :can_be_created, :compare_commits, :compare
state_machine :state, initial: :opened do
event :close do
@@ -137,9 +137,7 @@ class MergeRequest < ActiveRecord::Base
scope :by_milestone, ->(milestone) { where(milestone_id: milestone) }
scope :in_projects, ->(project_ids) { where("source_project_id in (:project_ids) OR target_project_id in (:project_ids)", project_ids: project_ids) }
scope :of_projects, ->(ids) { where(target_project_id: ids) }
- scope :opened, -> { with_state(:opened) }
scope :merged, -> { with_state(:merged) }
- scope :closed, -> { with_state(:closed) }
scope :closed_and_merged, -> { with_states(:closed, :merged) }
scope :join_project, -> { joins(:target_project) }
@@ -181,6 +179,10 @@ class MergeRequest < ActiveRecord::Base
merge_request_diff ? merge_request_diff.first_commit : compare_commits.first
end
+ def diff_size
+ merge_request_diff.size
+ end
+
def diff_base_commit
if merge_request_diff
merge_request_diff.base_commit
@@ -486,6 +488,16 @@ class MergeRequest < ActiveRecord::Base
end
end
+ def state_icon_name
+ if merged?
+ "check"
+ elsif closed?
+ "times"
+ else
+ "circle-o"
+ end
+ end
+
def target_sha
@target_sha ||= target_project.repository.commit(target_branch).sha
end
@@ -523,6 +535,29 @@ class MergeRequest < ActiveRecord::Base
end
end
+ def diverged_commits_count
+ cache = Rails.cache.read(:"merge_request_#{id}_diverged_commits")
+
+ if cache.blank? || cache[:source_sha] != source_sha || cache[:target_sha] != target_sha
+ cache = {
+ source_sha: source_sha,
+ target_sha: target_sha,
+ diverged_commits_count: compute_diverged_commits_count
+ }
+ Rails.cache.write(:"merge_request_#{id}_diverged_commits", cache)
+ end
+
+ cache[:diverged_commits_count]
+ end
+
+ def compute_diverged_commits_count
+ Gitlab::Git::Commit.between(target_project.repository.raw_repository, source_sha, target_sha).size
+ end
+
+ def diverged_from_target_branch?
+ diverged_commits_count > 0
+ end
+
def ci_commit
@ci_commit ||= source_project.ci_commit(last_commit.id) if last_commit && source_project
end
@@ -532,4 +567,12 @@ class MergeRequest < ActiveRecord::Base
[diff_base_commit, last_commit]
end
+
+ def merge_commit
+ @merge_commit ||= project.commit(merge_commit_sha) if merge_commit_sha
+ end
+
+ def can_be_reverted?(current_user = nil)
+ merge_commit && !merge_commit.has_been_reverted?(current_user, self)
+ end
end
diff --git a/app/models/merge_request_diff.rb b/app/models/merge_request_diff.rb
index c95179d6046..df08d3a6dfb 100644
--- a/app/models/merge_request_diff.rb
+++ b/app/models/merge_request_diff.rb
@@ -19,14 +19,15 @@ class MergeRequestDiff < ActiveRecord::Base
# Prevent store of diff if commits amount more then 500
COMMITS_SAFE_SIZE = 500
- attr_reader :commits, :diffs, :diffs_no_whitespace
-
belongs_to :merge_request
delegate :target_branch, :source_branch, to: :merge_request, prefix: nil
state_machine :state, initial: :empty do
state :collected
+ state :overflow
+ # Deprecated states: these are no longer used but these values may still occur
+ # in the database.
state :timeout
state :overflow_commits_safe_size
state :overflow_diff_files_limit
@@ -43,19 +44,23 @@ class MergeRequestDiff < ActiveRecord::Base
reload_diffs
end
- def diffs
- @diffs ||= (load_diffs(st_diffs) || [])
+ def size
+ real_size.presence || diffs.size
end
- def diffs_no_whitespace
- compare_result = Gitlab::CompareResult.new(
- Gitlab::Git::Compare.new(
- self.repository.raw_repository,
- self.target_branch,
- self.source_sha,
- ), { ignore_whitespace_change: true }
- )
- @diffs_no_whitespace ||= load_diffs(dump_commits(compare_result.diffs))
+ def diffs(options={})
+ if options[:ignore_whitespace_change]
+ @diffs_no_whitespace ||= begin
+ compare = Gitlab::Git::Compare.new(
+ self.repository.raw_repository,
+ self.target_branch,
+ self.source_sha,
+ )
+ compare.diffs(options)
+ end
+ else
+ @diffs ||= load_diffs(st_diffs, options)
+ end
end
def commits
@@ -94,16 +99,18 @@ class MergeRequestDiff < ActiveRecord::Base
end
end
- def load_diffs(raw)
- if raw.respond_to?(:map)
- raw.map { |hash| Gitlab::Git::Diff.new(hash) }
+ def load_diffs(raw, options)
+ if raw.respond_to?(:each)
+ Gitlab::Git::DiffCollection.new(raw, options)
+ else
+ Gitlab::Git::DiffCollection.new([])
end
end
# Collect array of Git::Commit objects
# between target and source branches
def unmerged_commits
- commits = compare_result.commits
+ commits = compare.commits
if commits.present?
commits = Commit.decorate(commits, merge_request.source_project).
@@ -133,27 +140,21 @@ class MergeRequestDiff < ActiveRecord::Base
if commits.size.zero?
self.state = :empty
- elsif commits.size > COMMITS_SAFE_SIZE
- self.state = :overflow_commits_safe_size
else
- new_diffs = unmerged_diffs
- end
+ diff_collection = unmerged_diffs
- if new_diffs.any?
- if new_diffs.size > Commit::DIFF_HARD_LIMIT_FILES
- self.state = :overflow_diff_files_limit
- new_diffs = new_diffs.first(Commit::DIFF_HARD_LIMIT_LINES)
+ if diff_collection.overflow?
+ # Set our state to 'overflow' to make the #empty? and #collected?
+ # methods (generated by StateMachine) return false.
+ self.state = :overflow
end
- if new_diffs.sum { |diff| diff.diff.lines.count } > Commit::DIFF_HARD_LIMIT_LINES
- self.state = :overflow_diff_lines_limit
- new_diffs = new_diffs.first(Commit::DIFF_HARD_LIMIT_LINES)
- end
- end
+ self.real_size = diff_collection.real_size
- if new_diffs.present?
- new_diffs = dump_commits(new_diffs)
- self.state = :collected
+ if diff_collection.any?
+ new_diffs = dump_diffs(diff_collection)
+ self.state = :collected
+ end
end
self.st_diffs = new_diffs
@@ -166,10 +167,7 @@ class MergeRequestDiff < ActiveRecord::Base
# Collect array of Git::Diff objects
# between target and source branches
def unmerged_diffs
- compare_result.diffs || []
- rescue Gitlab::Git::Diff::TimeoutError
- self.state = :timeout
- []
+ compare.diffs(Commit.max_diff_options)
end
def repository
@@ -181,18 +179,16 @@ class MergeRequestDiff < ActiveRecord::Base
source_commit.try(:sha)
end
- def compare_result
- @compare_result ||=
+ def compare
+ @compare ||=
begin
# Update ref for merge request
merge_request.fetch_ref
- Gitlab::CompareResult.new(
- Gitlab::Git::Compare.new(
- self.repository.raw_repository,
- self.target_branch,
- self.source_sha
- )
+ Gitlab::Git::Compare.new(
+ self.repository.raw_repository,
+ self.target_branch,
+ self.source_sha
)
end
end
diff --git a/app/models/milestone.rb b/app/models/milestone.rb
index 9c4476c768e..e3969f32dd6 100644
--- a/app/models/milestone.rb
+++ b/app/models/milestone.rb
@@ -24,11 +24,13 @@ class Milestone < ActiveRecord::Base
include Sortable
include Referable
include StripAttribute
+ include Milestoneish
belongs_to :project
has_many :issues
+ has_many :labels, -> { distinct.reorder('labels.title') }, through: :issues
has_many :merge_requests
- has_many :participants, through: :issues, source: :assignee
+ has_many :participants, -> { distinct.reorder('users.name') }, through: :issues, source: :assignee
scope :active, -> { with_state(:active) }
scope :closed, -> { with_state(:closed) }
@@ -91,24 +93,6 @@ class Milestone < ActiveRecord::Base
end
end
- def open_items_count
- self.issues.opened.count + self.merge_requests.opened.count
- end
-
- def closed_items_count
- self.issues.closed.count + self.merge_requests.closed_and_merged.count
- end
-
- def total_items_count
- self.issues.count + self.merge_requests.count
- end
-
- def percent_complete
- ((closed_items_count * 100) / total_items_count).abs
- rescue ZeroDivisionError
- 0
- end
-
def expires_at
if due_date
if due_date.past?
diff --git a/app/models/note.rb b/app/models/note.rb
index 55255d22c2f..3b20d5d22b6 100644
--- a/app/models/note.rb
+++ b/app/models/note.rb
@@ -37,6 +37,9 @@ class Note < ActiveRecord::Base
belongs_to :author, class_name: "User"
belongs_to :updated_by, class_name: "User"
+ has_many :todos, dependent: :destroy
+
+ delegate :gfm_reference, :local_reference, to: :noteable
delegate :name, to: :project, prefix: true
delegate :name, :email, to: :author, prefix: true
@@ -85,7 +88,7 @@ class Note < ActiveRecord::Base
next if discussion_ids.include?(note.discussion_id)
# don't group notes for the main target
- if !note.for_diff_line? && note.noteable_type == "MergeRequest"
+ if !note.for_diff_line? && note.for_merge_request?
discussions << [note]
else
discussions << notes.select do |other_note|
@@ -129,9 +132,11 @@ class Note < ActiveRecord::Base
end
def find_diff
- return nil unless noteable && noteable.diffs.present?
+ return nil unless noteable
+ return @diff if defined?(@diff)
- @diff ||= noteable.diffs.find do |d|
+ # Don't use ||= because nil is a valid value for @diff
+ @diff = noteable.diffs(Commit.max_diff_options).find do |d|
Digest::SHA1.hexdigest(d.new_path) == diff_file_index if d.new_path
end
end
@@ -163,20 +168,16 @@ class Note < ActiveRecord::Base
def active?
return true unless self.diff
return false unless noteable
+ return @active if defined?(@active)
- noteable.diffs.each do |mr_diff|
- next unless mr_diff.new_path == self.diff.new_path
+ diffs = noteable.diffs(Commit.max_diff_options)
+ notable_diff = diffs.find { |d| d.new_path == self.diff.new_path }
- lines = Gitlab::Diff::Parser.new.parse(mr_diff.diff.lines.to_a)
+ return @active = false if notable_diff.nil?
- lines.each do |line|
- if line.text == diff_line
- return true
- end
- end
- end
-
- false
+ parsed_lines = Gitlab::Diff::Parser.new.parse(notable_diff.diff.each_line)
+ # We cannot use ||= because @active may be false
+ @active = parsed_lines.any? { |line_obj| line_obj.text == diff_line }
end
def outdated?
@@ -261,7 +262,7 @@ class Note < ActiveRecord::Base
end
def diff_lines
- @diff_lines ||= Gitlab::Diff::Parser.new.parse(diff.diff.lines)
+ @diff_lines ||= Gitlab::Diff::Parser.new.parse(diff.diff.each_line)
end
def highlighted_diff_lines
@@ -313,20 +314,6 @@ class Note < ActiveRecord::Base
nil
end
- # Mentionable override.
- def gfm_reference(from_project = nil)
- noteable.gfm_reference(from_project)
- end
-
- # Mentionable override.
- def local_reference
- noteable
- end
-
- def noteable_type_name
- noteable_type.downcase if noteable_type.present?
- end
-
# FIXME: Hack for polymorphic associations with STI
# For more information visit http://api.rubyonrails.org/classes/ActiveRecord/Associations/ClassMethods.html#label-Polymorphic+Associations
def noteable_type=(noteable_type)
@@ -346,10 +333,6 @@ class Note < ActiveRecord::Base
Event.reset_event_cache_for(self)
end
- def system?
- read_attribute(:system)
- end
-
def downvote?
is_award && note == "thumbsdown"
end
@@ -375,6 +358,7 @@ class Note < ActiveRecord::Base
#
def set_award!
return unless awards_supported? && contains_emoji_only?
+
self.is_award = true
self.note = award_emoji_name
end
@@ -382,7 +366,7 @@ class Note < ActiveRecord::Base
private
def awards_supported?
- noteable.kind_of?(Issue) || noteable.is_a?(MergeRequest)
+ (for_issue? || for_merge_request?) && !for_diff_line?
end
def contains_emoji_only?
diff --git a/app/models/project.rb b/app/models/project.rb
index f11c6d7c6be..3235a1cee50 100644
--- a/app/models/project.rb
+++ b/app/models/project.rb
@@ -215,6 +215,7 @@ class Project < ActiveRecord::Base
scope :public_only, -> { where(visibility_level: Project::PUBLIC) }
scope :public_and_internal_only, -> { where(visibility_level: Project.public_and_internal_levels) }
scope :non_archived, -> { where(archived: false) }
+ scope :for_milestones, ->(ids) { joins(:milestones).where('milestones.id' => ids).distinct }
state_machine :import_status, initial: :none do
event :import_start do
@@ -278,7 +279,7 @@ class Project < ActiveRecord::Base
end
def search_by_title(query)
- where('projects.archived = ?', false).where('LOWER(projects.name) LIKE :query', query: "%#{query.downcase}%")
+ non_archived.where('LOWER(projects.name) LIKE :query', query: "%#{query.downcase}%")
end
def find_with_namespace(id)
@@ -342,7 +343,7 @@ class Project < ActiveRecord::Base
end
def repository
- @repository ||= Repository.new(path_with_namespace, nil, self)
+ @repository ||= Repository.new(path_with_namespace, self)
end
def commit(id = 'HEAD')
@@ -382,6 +383,10 @@ class Project < ActiveRecord::Base
external_import? || forked?
end
+ def no_import?
+ import_status == 'none'
+ end
+
def external_import?
import_url.present?
end
@@ -707,6 +712,8 @@ class Project < ActiveRecord::Base
old_path_with_namespace = File.join(namespace_dir, path_was)
new_path_with_namespace = File.join(namespace_dir, path)
+ expire_caches_before_rename(old_path_with_namespace)
+
if gitlab_shell.mv_repository(old_path_with_namespace, new_path_with_namespace)
# If repository moved successfully we need to send update instructions to users.
# However we cannot allow rollback since we moved repository
@@ -735,14 +742,39 @@ class Project < ActiveRecord::Base
Gitlab::UploadsTransfer.new.rename_project(path_was, path, namespace.path)
end
+ # Expires various caches before a project is renamed.
+ def expire_caches_before_rename(old_path)
+ repo = Repository.new(old_path, self)
+ wiki = Repository.new("#{old_path}.wiki", self)
+
+ if repo.exists?
+ repo.expire_cache
+ repo.expire_emptiness_caches
+ end
+
+ if wiki.exists?
+ wiki.expire_cache
+ wiki.expire_emptiness_caches
+ end
+ end
+
def hook_attrs
{
name: name,
- ssh_url: ssh_url_to_repo,
- http_url: http_url_to_repo,
+ description: description,
web_url: web_url,
+ avatar_url: avatar_url,
+ git_ssh_url: ssh_url_to_repo,
+ git_http_url: http_url_to_repo,
namespace: namespace.name,
- visibility_level: visibility_level
+ visibility_level: visibility_level,
+ path_with_namespace: path_with_namespace,
+ default_branch: default_branch,
+ # Backward compatibility
+ homepage: web_url,
+ url: url_to_repo,
+ ssh_url: ssh_url_to_repo,
+ http_url: http_url_to_repo
}
end
@@ -788,10 +820,7 @@ class Project < ActiveRecord::Base
end
def change_head(branch)
- # Cached divergent commit counts are based on repository head
- repository.expire_branch_cache
- repository.expire_root_ref_cache
-
+ repository.before_change_head
gitlab_shell.update_repository_head(self.path_with_namespace, branch)
reload_default_branch
end
diff --git a/app/models/project_services/jira_service.rb b/app/models/project_services/jira_service.rb
index f6571fc063e..aba37921c09 100644
--- a/app/models/project_services/jira_service.rb
+++ b/app/models/project_services/jira_service.rb
@@ -108,7 +108,8 @@ class JiraService < IssueTrackerService
},
entity: {
name: noteable_name.humanize.downcase,
- url: entity_url
+ url: entity_url,
+ title: noteable.title
}
}
@@ -196,10 +197,11 @@ class JiraService < IssueTrackerService
user_url = data[:user][:url]
entity_name = data[:entity][:name]
entity_url = data[:entity][:url]
+ entity_title = data[:entity][:title]
project_name = data[:project][:name]
message = {
- body: "[#{user_name}|#{user_url}] mentioned this issue in [a #{entity_name} of #{project_name}|#{entity_url}]."
+ body: %Q{[#{user_name}|#{user_url}] mentioned this issue in [a #{entity_name} of #{project_name}|#{entity_url}]:\n'#{entity_title}'}
}
unless existing_comment?(issue_name, message[:body])
diff --git a/app/models/project_services/pushover_service.rb b/app/models/project_services/pushover_service.rb
index 3d7e8bbee61..e76d9eca2ab 100644
--- a/app/models/project_services/pushover_service.rb
+++ b/app/models/project_services/pushover_service.rb
@@ -112,7 +112,7 @@ class PushoverService < Service
priority: priority,
title: "#{project.name_with_namespace}",
message: message,
- url: data[:repository][:homepage],
+ url: data[:project][:web_url],
url_title: "See project #{project.name_with_namespace}"
}
diff --git a/app/models/project_team.rb b/app/models/project_team.rb
index 9f380a382cb..9629c7e1bb9 100644
--- a/app/models/project_team.rb
+++ b/app/models/project_team.rb
@@ -136,7 +136,7 @@ class ProjectTeam
end
def human_max_access(user_id)
- Gitlab::Access.options.key max_member_access(user_id)
+ Gitlab::Access.options_with_owner.key(max_member_access(user_id))
end
# This method assumes project and group members are eager loaded for optimal
diff --git a/app/models/project_wiki.rb b/app/models/project_wiki.rb
index c847eba8d1c..c96e6f0b8ea 100644
--- a/app/models/project_wiki.rb
+++ b/app/models/project_wiki.rb
@@ -123,7 +123,7 @@ class ProjectWiki
end
def repository
- Repository.new(path_with_namespace, default_branch, @project)
+ Repository.new(path_with_namespace, @project)
end
def default_branch
diff --git a/app/models/repository.rb b/app/models/repository.rb
index 7f0047a002e..c135ab61f6a 100644
--- a/app/models/repository.rb
+++ b/app/models/repository.rb
@@ -15,7 +15,7 @@ class Repository
Gitlab::Popen.popen(%W(find #{repository_downloads_path} -not -path #{repository_downloads_path} -mmin +120 -delete))
end
- def initialize(path_with_namespace, default_branch = nil, project = nil)
+ def initialize(path_with_namespace, project)
@path_with_namespace = path_with_namespace
@project = project
end
@@ -23,13 +23,11 @@ class Repository
def raw_repository
return nil unless path_with_namespace
- @raw_repository ||= begin
- repo = Gitlab::Git::Repository.new(path_to_repo)
- repo.autocrlf = :input
- repo
- rescue Gitlab::Git::Repository::NoRepository
- nil
- end
+ @raw_repository ||= Gitlab::Git::Repository.new(path_to_repo)
+ end
+
+ def update_autocrlf_option
+ raw_repository.autocrlf = :input if raw_repository.autocrlf != :input
end
# Return absolute path to repository
@@ -40,7 +38,12 @@ class Repository
end
def exists?
- raw_repository
+ return false unless raw_repository
+
+ raw_repository.rugged
+ true
+ rescue Gitlab::Git::Repository::NoRepository
+ false
end
def empty?
@@ -67,7 +70,7 @@ class Repository
end
def commit(id = 'HEAD')
- return nil unless raw_repository
+ return nil unless exists?
commit = Gitlab::Git::Commit.find(raw_repository, id)
commit = Commit.new(commit, @project) if commit
commit
@@ -236,6 +239,10 @@ class Repository
end
expire_branch_cache(branch_name)
+
+ # This ensures this particular cache is flushed after the first commit to a
+ # new repository.
+ expire_emptiness_caches if empty?
end
def expire_branch_cache(branch_name = nil)
@@ -258,6 +265,14 @@ class Repository
@root_ref = nil
end
+ # Expires the cache(s) used to determine if a repository is empty or not.
+ def expire_emptiness_caches
+ cache.expire(:empty?)
+ @empty = nil
+
+ expire_has_visible_content_cache
+ end
+
def expire_has_visible_content_cache
cache.expire(:has_visible_content?)
@has_visible_content = nil
@@ -283,6 +298,46 @@ class Repository
cache.expire(:branch_names)
end
+ # Runs code just before a repository is deleted.
+ def before_delete
+ expire_cache if exists?
+
+ expire_root_ref_cache
+ expire_emptiness_caches
+ end
+
+ # Runs code just before the HEAD of a repository is changed.
+ def before_change_head
+ # Cached divergent commit counts are based on repository head
+ expire_branch_cache
+ expire_root_ref_cache
+ end
+
+ # Runs code before creating a new tag.
+ def before_create_tag
+ expire_cache
+ end
+
+ # Runs code after a repository has been forked/imported.
+ def after_import
+ expire_emptiness_caches
+ end
+
+ # Runs code after a new commit has been pushed.
+ def after_push_commit(branch_name)
+ expire_cache(branch_name)
+ end
+
+ # Runs code after a new branch has been created.
+ def after_create_branch
+ expire_has_visible_content_cache
+ end
+
+ # Runs code after an existing branch has been removed.
+ def after_remove_branch
+ expire_has_visible_content_cache
+ end
+
def method_missing(m, *args, &block)
if m == :lookup && !block_given?
lookup_cache[m] ||= {}
@@ -599,6 +654,42 @@ class Repository
end
end
+ def revert(user, commit, base_branch, revert_tree_id = nil)
+ source_sha = find_branch(base_branch).target
+ revert_tree_id ||= check_revert_content(commit, base_branch)
+
+ return false unless revert_tree_id
+
+ commit_with_hooks(user, base_branch) do |ref|
+ committer = user_to_committer(user)
+ source_sha = Rugged::Commit.create(rugged,
+ message: commit.revert_message,
+ author: committer,
+ committer: committer,
+ tree: revert_tree_id,
+ parents: [rugged.lookup(source_sha)],
+ update_ref: ref)
+ end
+ end
+
+ def check_revert_content(commit, base_branch)
+ source_sha = find_branch(base_branch).target
+ args = [commit.id, source_sha]
+ args << { mainline: 1 } if commit.merge_commit?
+
+ revert_index = rugged.revert_commit(*args)
+ return false if revert_index.conflicts?
+
+ tree_id = revert_index.write_tree(rugged)
+ return false unless diff_exists?(source_sha, tree_id)
+
+ tree_id
+ end
+
+ def diff_exists?(sha1, sha2)
+ rugged.diff(sha1, sha2).size > 0
+ end
+
def merged_to_root_ref?(branch_name)
branch_commit = commit(branch_name)
root_ref_commit = commit(root_ref)
@@ -611,6 +702,8 @@ class Repository
end
def merge_base(first_commit_id, second_commit_id)
+ first_commit_id = commit(first_commit_id).try(:id) || first_commit_id
+ second_commit_id = commit(second_commit_id).try(:id) || second_commit_id
rugged.merge_base(first_commit_id, second_commit_id)
rescue Rugged::ReferenceError
nil
@@ -674,12 +767,15 @@ class Repository
end
def commit_with_hooks(current_user, branch)
+ update_autocrlf_option
+
oldrev = Gitlab::Git::BLANK_SHA
ref = Gitlab::Git::BRANCH_REF_PREFIX + branch
+ target_branch = find_branch(branch)
was_empty = empty?
- unless was_empty
- oldrev = find_branch(branch).target
+ if !was_empty && target_branch
+ oldrev = target_branch.target
end
with_tmp_ref(oldrev) do |tmp_ref|
@@ -691,7 +787,7 @@ class Repository
end
GitHooksService.new.execute(current_user, path_to_repo, oldrev, newrev, ref) do
- if was_empty
+ if was_empty || !target_branch
# Create branch
rugged.references.create(ref, newrev)
else
@@ -706,6 +802,8 @@ class Repository
end
end
end
+
+ newrev
end
end
diff --git a/app/models/todo.rb b/app/models/todo.rb
new file mode 100644
index 00000000000..5f91991f781
--- /dev/null
+++ b/app/models/todo.rb
@@ -0,0 +1,53 @@
+# == Schema Information
+#
+# Table name: todos
+#
+# id :integer not null, primary key
+# user_id :integer not null
+# project_id :integer not null
+# target_id :integer not null
+# target_type :string not null
+# author_id :integer
+# note_id :integer
+# action :integer not null
+# state :string not null
+# created_at :datetime
+# updated_at :datetime
+#
+
+class Todo < ActiveRecord::Base
+ ASSIGNED = 1
+ MENTIONED = 2
+
+ belongs_to :author, class_name: "User"
+ belongs_to :note
+ belongs_to :project
+ belongs_to :target, polymorphic: true, touch: true
+ belongs_to :user
+
+ delegate :name, :email, to: :author, prefix: true, allow_nil: true
+
+ validates :action, :project, :target, :user, presence: true
+
+ default_scope { reorder(id: :desc) }
+
+ scope :pending, -> { with_state(:pending) }
+ scope :done, -> { with_state(:done) }
+
+ state_machine :state, initial: :pending do
+ event :done do
+ transition [:pending, :done] => :done
+ end
+
+ state :pending
+ state :done
+ end
+
+ def body
+ if note.present?
+ note.note
+ else
+ target.title
+ end
+ end
+end
diff --git a/app/models/user.rb b/app/models/user.rb
index 9fe94b13e52..3098d49d58a 100644
--- a/app/models/user.rb
+++ b/app/models/user.rb
@@ -98,6 +98,9 @@ class User < ActiveRecord::Base
# Virtual attribute for authenticating by either username or email
attr_accessor :login
+ # Virtual attributes to define avatar cropping
+ attr_accessor :avatar_crop_x, :avatar_crop_y, :avatar_crop_size
+
#
# Relations
#
@@ -140,7 +143,7 @@ class User < ActiveRecord::Base
has_one :abuse_report, dependent: :destroy
has_many :spam_logs, dependent: :destroy
has_many :builds, dependent: :nullify, class_name: 'Ci::Build'
-
+ has_many :todos, dependent: :destroy
#
# Validations
@@ -163,6 +166,11 @@ class User < ActiveRecord::Base
validate :owns_public_email, if: ->(user) { user.public_email_changed? }
validates :avatar, file_size: { maximum: 200.kilobytes.to_i }
+ validates :avatar_crop_x, :avatar_crop_y, :avatar_crop_size,
+ numericality: { only_integer: true },
+ presence: true,
+ if: ->(user) { user.avatar? && user.avatar_changed? }
+
before_validation :generate_password, on: :create
before_validation :restricted_signup_domains, on: :create
before_validation :sanitize_attrs
@@ -354,17 +362,19 @@ class User < ActiveRecord::Base
def disable_two_factor!
update_attributes(
- two_factor_enabled: false,
- encrypted_otp_secret: nil,
- encrypted_otp_secret_iv: nil,
- encrypted_otp_secret_salt: nil,
- otp_backup_codes: nil
+ two_factor_enabled: false,
+ encrypted_otp_secret: nil,
+ encrypted_otp_secret_iv: nil,
+ encrypted_otp_secret_salt: nil,
+ otp_grace_period_started_at: nil,
+ otp_backup_codes: nil
)
end
def namespace_uniq
# Return early if username already failed the first uniqueness validation
- return if self.errors[:username].include?('has already been taken')
+ return if self.errors.key?(:username) &&
+ self.errors[:username].include?('has already been taken')
namespace_name = self.username
existing_namespace = Namespace.by_path(namespace_name)
diff --git a/app/services/archive_repository_service.rb b/app/services/archive_repository_service.rb
deleted file mode 100644
index 2160bf13e6d..00000000000
--- a/app/services/archive_repository_service.rb
+++ /dev/null
@@ -1,23 +0,0 @@
-class ArchiveRepositoryService
- attr_reader :project, :ref, :format
-
- def initialize(project, ref, format)
- format ||= 'tar.gz'
- @project, @ref, @format = project, ref, format.downcase
- end
-
- def execute(options = {})
- RepositoryArchiveCacheWorker.perform_async
-
- metadata = project.repository.archive_metadata(ref, storage_path, format)
- raise "Repository or ref not found" if metadata.empty?
-
- metadata
- end
-
- private
-
- def storage_path
- Gitlab.config.gitlab.repository_downloads_path
- end
-end
diff --git a/app/services/base_service.rb b/app/services/base_service.rb
index b48ca67d4d2..8563633816c 100644
--- a/app/services/base_service.rb
+++ b/app/services/base_service.rb
@@ -23,6 +23,10 @@ class BaseService
EventCreateService.new
end
+ def todo_service
+ TodoService.new
+ end
+
def log_info(message)
Gitlab::AppLogger.info message
end
diff --git a/app/services/ci/create_builds_service.rb b/app/services/ci/create_builds_service.rb
index ad901f2da5d..002f7ba1278 100644
--- a/app/services/ci/create_builds_service.rb
+++ b/app/services/ci/create_builds_service.rb
@@ -34,6 +34,7 @@ module Ci
build = commit.builds.create!(build_attrs)
build.execute_hooks
+ build
end
end
end
diff --git a/app/services/commits/revert_service.rb b/app/services/commits/revert_service.rb
new file mode 100644
index 00000000000..9cb918d7a2e
--- /dev/null
+++ b/app/services/commits/revert_service.rb
@@ -0,0 +1,58 @@
+module Commits
+ class RevertService < ::BaseService
+ class ValidationError < StandardError; end
+ class ReversionError < StandardError; end
+
+ def execute
+ @source_project = params[:source_project] || @project
+ @target_branch = params[:target_branch]
+ @commit = params[:commit]
+ @create_merge_request = params[:create_merge_request].present?
+
+ validate and commit
+ rescue Repository::CommitError, Gitlab::Git::Repository::InvalidBlobName, GitHooksService::PreReceiveError,
+ ValidationError, ReversionError => ex
+ error(ex.message)
+ end
+
+ def commit
+ revert_into = @create_merge_request ? @commit.revert_branch_name : @target_branch
+ revert_tree_id = repository.check_revert_content(@commit, @target_branch)
+
+ if revert_tree_id
+ create_target_branch(revert_into) if @create_merge_request
+
+ repository.revert(current_user, @commit, revert_into, revert_tree_id)
+ success
+ else
+ error_msg = "Sorry, we cannot revert this #{params[:revert_type_title]} automatically.
+ It may have already been reverted, or a more recent commit may have updated some of its content."
+ raise ReversionError, error_msg
+ end
+ end
+
+ private
+
+ def create_target_branch(new_branch)
+ # Temporary branch exists and contains the revert commit
+ return success if repository.find_branch(new_branch)
+
+ result = CreateBranchService.new(@project, current_user)
+ .execute(new_branch, @target_branch, source_project: @source_project)
+
+ if result[:status] == :error
+ raise ReversionError, "There was an error creating the source branch: #{result[:message]}"
+ end
+ end
+
+ def validate
+ allowed = ::Gitlab::GitAccess.new(current_user, project).can_push_to_branch?(@target_branch)
+
+ unless allowed
+ raise_error('You are not allowed to push into this branch')
+ end
+
+ true
+ end
+ end
+end
diff --git a/app/services/compare_service.rb b/app/services/compare_service.rb
index ec581658fc1..e2bccbdbcc3 100644
--- a/app/services/compare_service.rb
+++ b/app/services/compare_service.rb
@@ -1,7 +1,7 @@
require 'securerandom'
# Compare 2 branches for one repo or between repositories
-# and return Gitlab::CompareResult object that responds to commits and diffs
+# and return Gitlab::Git::Compare object that responds to commits and diffs
class CompareService
def execute(source_project, source_branch, target_project, target_branch, diff_options = {})
source_commit = source_project.commit(source_branch)
@@ -20,12 +20,10 @@ class CompareService
)
end
- Gitlab::CompareResult.new(
- Gitlab::Git::Compare.new(
- target_project.repository.raw_repository,
- target_branch,
- source_sha,
- ), diff_options
+ Gitlab::Git::Compare.new(
+ target_project.repository.raw_repository,
+ target_branch,
+ source_sha,
)
end
end
diff --git a/app/services/git_push_service.rb b/app/services/git_push_service.rb
index e3bf14966c8..9ba200f7bde 100644
--- a/app/services/git_push_service.rb
+++ b/app/services/git_push_service.rb
@@ -1,10 +1,10 @@
-class GitPushService
- attr_accessor :project, :user, :push_data, :push_commits
+class GitPushService < BaseService
+ attr_accessor :push_data, :push_commits
include Gitlab::CurrentSettings
include Gitlab::Access
# This method will be called after each git update
- # and only if the provided user and project is present in GitLab.
+ # and only if the provided user and project are present in GitLab.
#
# All callbacks for post receive action should be placed here.
#
@@ -15,67 +15,67 @@ class GitPushService
# 4. Executes the project's web hooks
# 5. Executes the project's services
#
- def execute(project, user, oldrev, newrev, ref)
- @project, @user = project, user
-
- branch_name = Gitlab::Git.ref_name(ref)
-
- project.repository.expire_cache(branch_name)
-
- if push_remove_branch?(ref, newrev)
- project.repository.expire_has_visible_content_cache
+ def execute
+ @project.repository.after_push_commit(branch_name)
+ if push_remove_branch?
+ @project.repository.after_remove_branch
@push_commits = []
- elsif push_to_new_branch?(ref, oldrev)
- project.repository.expire_has_visible_content_cache
+ elsif push_to_new_branch?
+ @project.repository.after_create_branch
# Re-find the pushed commits.
- if is_default_branch?(ref)
+ if is_default_branch?
# Initial push to the default branch. Take the full history of that branch as "newly pushed".
- @push_commits = project.repository.commits(newrev)
-
- # Ensure HEAD points to the default branch in case it is not master
- project.change_head(branch_name)
-
- # Set protection on the default branch if configured
- if (current_application_settings.default_branch_protection != PROTECTION_NONE)
- developers_can_push = current_application_settings.default_branch_protection == PROTECTION_DEV_CAN_PUSH ? true : false
- project.protected_branches.create({ name: project.default_branch, developers_can_push: developers_can_push })
- end
+ process_default_branch
else
# Use the pushed commits that aren't reachable by the default branch
# as a heuristic. This may include more commits than are actually pushed, but
# that shouldn't matter because we check for existing cross-references later.
- @push_commits = project.repository.commits_between(project.default_branch, newrev)
+ @push_commits = @project.repository.commits_between(@project.default_branch, params[:newrev])
# don't process commits for the initial push to the default branch
- process_commit_messages(ref)
+ process_commit_messages
end
- elsif push_to_existing_branch?(ref, oldrev)
+ elsif push_to_existing_branch?
# Collect data for this git push
- @push_commits = project.repository.commits_between(oldrev, newrev)
- process_commit_messages(ref)
+ @push_commits = @project.repository.commits_between(params[:oldrev], params[:newrev])
+ process_commit_messages
end
-
# Update merge requests that may be affected by this push. A new branch
# could cause the last commit of a merge request to change.
- project.update_merge_requests(oldrev, newrev, ref, @user)
+ update_merge_requests
+ end
- @push_data = build_push_data(oldrev, newrev, ref)
+ protected
- EventCreateService.new.push(project, user, @push_data)
- project.execute_hooks(@push_data.dup, :push_hooks)
- project.execute_services(@push_data.dup, :push_hooks)
- CreateCommitBuildsService.new.execute(project, @user, @push_data)
- ProjectCacheWorker.perform_async(project.id)
+ def update_merge_requests
+ @project.update_merge_requests(params[:oldrev], params[:newrev], params[:ref], current_user)
+
+ EventCreateService.new.push(@project, current_user, build_push_data)
+ @project.execute_hooks(build_push_data.dup, :push_hooks)
+ @project.execute_services(build_push_data.dup, :push_hooks)
+ CreateCommitBuildsService.new.execute(@project, current_user, build_push_data)
+ ProjectCacheWorker.perform_async(@project.id)
end
- protected
+ def process_default_branch
+ @push_commits = project.repository.commits(params[:newrev])
+
+ # Ensure HEAD points to the default branch in case it is not master
+ project.change_head(branch_name)
+
+ # Set protection on the default branch if configured
+ if (current_application_settings.default_branch_protection != PROTECTION_NONE)
+ developers_can_push = current_application_settings.default_branch_protection == PROTECTION_DEV_CAN_PUSH ? true : false
+ @project.protected_branches.create({ name: @project.default_branch, developers_can_push: developers_can_push })
+ end
+ end
# Extract any GFM references from the pushed commit messages. If the configured issue-closing regex is matched,
# close the referenced Issue. Create cross-reference Notes corresponding to any other referenced Mentionables.
- def process_commit_messages(ref)
- is_default_branch = is_default_branch?(ref)
+ def process_commit_messages
+ is_default_branch = is_default_branch?
authors = Hash.new do |hash, commit|
email = commit.author_email
@@ -94,7 +94,7 @@ class GitPushService
# Close issues if these commits were pushed to the project's default branch and the commit message matches the
# closing regex. Exclude any mentioned Issues from cross-referencing even if the commits are being pushed to
# a different branch.
- closed_issues = commit.closes_issues(user)
+ closed_issues = commit.closes_issues(current_user)
closed_issues.each do |issue|
Issues::CloseService.new(project, authors[commit], {}).execute(issue, commit)
end
@@ -104,34 +104,38 @@ class GitPushService
end
end
- def build_push_data(oldrev, newrev, ref)
- Gitlab::PushDataBuilder.
- build(project, user, oldrev, newrev, ref, push_commits)
+ def build_push_data
+ @push_data ||= Gitlab::PushDataBuilder.
+ build(@project, current_user, params[:oldrev], params[:newrev], params[:ref], push_commits)
end
- def push_to_existing_branch?(ref, oldrev)
+ def push_to_existing_branch?
# Return if this is not a push to a branch (e.g. new commits)
- Gitlab::Git.branch_ref?(ref) && !Gitlab::Git.blank_ref?(oldrev)
+ Gitlab::Git.branch_ref?(params[:ref]) && !Gitlab::Git.blank_ref?(params[:oldrev])
end
- def push_to_new_branch?(ref, oldrev)
- Gitlab::Git.branch_ref?(ref) && Gitlab::Git.blank_ref?(oldrev)
+ def push_to_new_branch?
+ Gitlab::Git.branch_ref?(params[:ref]) && Gitlab::Git.blank_ref?(params[:oldrev])
end
- def push_remove_branch?(ref, newrev)
- Gitlab::Git.branch_ref?(ref) && Gitlab::Git.blank_ref?(newrev)
+ def push_remove_branch?
+ Gitlab::Git.branch_ref?(params[:ref]) && Gitlab::Git.blank_ref?(params[:newrev])
end
- def push_to_branch?(ref)
- Gitlab::Git.branch_ref?(ref)
+ def push_to_branch?
+ Gitlab::Git.branch_ref?(params[:ref])
end
- def is_default_branch?(ref)
- Gitlab::Git.branch_ref?(ref) &&
- (Gitlab::Git.ref_name(ref) == project.default_branch || project.default_branch.nil?)
+ def is_default_branch?
+ Gitlab::Git.branch_ref?(params[:ref]) &&
+ (Gitlab::Git.ref_name(params[:ref]) == project.default_branch || project.default_branch.nil?)
end
def commit_user(commit)
- commit.author || user
+ commit.author || current_user
+ end
+
+ def branch_name
+ @branch_name ||= Gitlab::Git.ref_name(params[:ref])
end
end
diff --git a/app/services/git_tag_push_service.rb b/app/services/git_tag_push_service.rb
index 4144c7111d0..a62c5fc4fc4 100644
--- a/app/services/git_tag_push_service.rb
+++ b/app/services/git_tag_push_service.rb
@@ -2,7 +2,7 @@ class GitTagPushService
attr_accessor :project, :user, :push_data
def execute(project, user, oldrev, newrev, ref)
- project.repository.expire_cache
+ project.repository.before_create_tag
@project, @user = project, user
@push_data = build_push_data(oldrev, newrev, ref)
diff --git a/app/services/issuable_base_service.rb b/app/services/issuable_base_service.rb
index 2556f06e2d3..ca87dca4a70 100644
--- a/app/services/issuable_base_service.rb
+++ b/app/services/issuable_base_service.rb
@@ -54,7 +54,7 @@ class IssuableBaseService < BaseService
if params.present? && issuable.update_attributes(params.merge(updated_by: current_user))
issuable.reset_events_cache
handle_common_system_notes(issuable, old_labels: old_labels)
- handle_changes(issuable)
+ handle_changes(issuable, old_labels: old_labels)
issuable.create_new_cross_references!(current_user)
execute_hooks(issuable, 'update')
end
@@ -71,6 +71,19 @@ class IssuableBaseService < BaseService
end
end
+ def has_changes?(issuable, options = {})
+ valid_attrs = [:title, :description, :assignee_id, :milestone_id, :target_branch]
+
+ attrs_changed = valid_attrs.any? do |attr|
+ issuable.previous_changes.include?(attr.to_s)
+ end
+
+ old_labels = options[:old_labels]
+ labels_changed = old_labels && issuable.labels != old_labels
+
+ attrs_changed || labels_changed
+ end
+
def handle_common_system_notes(issuable, options = {})
if issuable.previous_changes.include?('title')
create_title_change_note(issuable, issuable.previous_changes['title'].first)
diff --git a/app/services/issues/close_service.rb b/app/services/issues/close_service.rb
index a1a20e47681..78254b49af3 100644
--- a/app/services/issues/close_service.rb
+++ b/app/services/issues/close_service.rb
@@ -3,6 +3,7 @@ module Issues
def execute(issue, commit = nil)
if project.jira_tracker? && project.jira_service.active
project.jira_service.execute(commit, issue)
+ todo_service.close_issue(issue, current_user)
return issue
end
@@ -10,6 +11,7 @@ module Issues
event_service.close_issue(issue, current_user)
create_note(issue, commit)
notification_service.close_issue(issue, current_user)
+ todo_service.close_issue(issue, current_user)
execute_hooks(issue, 'close')
end
diff --git a/app/services/issues/create_service.rb b/app/services/issues/create_service.rb
index bcb380d3215..10787e8873c 100644
--- a/app/services/issues/create_service.rb
+++ b/app/services/issues/create_service.rb
@@ -9,6 +9,7 @@ module Issues
if issue.save
issue.update_attributes(label_ids: label_params)
notification_service.new_issue(issue, current_user)
+ todo_service.new_issue(issue, current_user)
event_service.open_issue(issue, current_user)
issue.create_cross_references!(current_user)
execute_hooks(issue, 'open')
diff --git a/app/services/issues/update_service.rb b/app/services/issues/update_service.rb
index a55a04dd5e0..51ef9dfe610 100644
--- a/app/services/issues/update_service.rb
+++ b/app/services/issues/update_service.rb
@@ -4,7 +4,16 @@ module Issues
update(issue)
end
- def handle_changes(issue)
+ def handle_changes(issue, options = {})
+ if has_changes?(issue, options)
+ todo_service.mark_pending_todos_as_done(issue, current_user)
+ end
+
+ if issue.previous_changes.include?('title') ||
+ issue.previous_changes.include?('description')
+ todo_service.update_issue(issue, current_user)
+ end
+
if issue.previous_changes.include?('milestone_id')
create_milestone_note(issue)
end
@@ -12,6 +21,7 @@ module Issues
if issue.previous_changes.include?('assignee_id')
create_assignee_note(issue)
notification_service.reassigned_issue(issue, current_user)
+ todo_service.reassigned_issue(issue, current_user)
end
end
diff --git a/app/services/merge_requests/build_service.rb b/app/services/merge_requests/build_service.rb
index a9b29f9654d..954746a39a5 100644
--- a/app/services/merge_requests/build_service.rb
+++ b/app/services/merge_requests/build_service.rb
@@ -5,9 +5,7 @@ module MergeRequests
# Set MR attributes
merge_request.can_be_created = false
- merge_request.compare_failed = false
merge_request.compare_commits = []
- merge_request.compare_diffs = []
merge_request.source_project = project unless merge_request.source_project
merge_request.target_project ||= (project.forked_from_project || project)
merge_request.target_branch ||= merge_request.target_project.default_branch
@@ -21,42 +19,30 @@ module MergeRequests
return build_failed(merge_request, message)
end
- compare_result = CompareService.new.execute(
+ compare = CompareService.new.execute(
merge_request.source_project,
merge_request.source_branch,
merge_request.target_project,
merge_request.target_branch,
)
- commits = compare_result.commits
+ commits = compare.commits
# At this point we decide if merge request can be created
# If we have at least one commit to merge -> creation allowed
if commits.present?
merge_request.compare_commits = Commit.decorate(commits, merge_request.source_project)
merge_request.can_be_created = true
- merge_request.compare_failed = false
-
- # Try to collect diff for merge request.
- diffs = compare_result.diffs
-
- if diffs.present?
- merge_request.compare_diffs = diffs
-
- elsif diffs == false
- merge_request.can_be_created = false
- merge_request.compare_failed = true
- end
+ merge_request.compare = compare
else
merge_request.can_be_created = false
- merge_request.compare_failed = false
end
commits = merge_request.compare_commits
if commits && commits.count == 1
commit = commits.first
merge_request.title = commit.title
- merge_request.description = commit.description.try(:strip)
+ merge_request.description ||= commit.description.try(:strip)
else
merge_request.title = merge_request.source_branch.titleize.humanize
end
diff --git a/app/services/merge_requests/close_service.rb b/app/services/merge_requests/close_service.rb
index 47454f9f0c2..27ee81fe3e7 100644
--- a/app/services/merge_requests/close_service.rb
+++ b/app/services/merge_requests/close_service.rb
@@ -9,6 +9,7 @@ module MergeRequests
event_service.close_mr(merge_request, current_user)
create_note(merge_request)
notification_service.close_mr(merge_request, current_user)
+ todo_service.close_merge_request(merge_request, current_user)
execute_hooks(merge_request, 'close')
end
diff --git a/app/services/merge_requests/create_service.rb b/app/services/merge_requests/create_service.rb
index 009d5a6867e..33609d01f20 100644
--- a/app/services/merge_requests/create_service.rb
+++ b/app/services/merge_requests/create_service.rb
@@ -2,7 +2,7 @@ module MergeRequests
class CreateService < MergeRequests::BaseService
def execute
# @project is used to determine whether the user can set the merge request's
- # assignee, milestone and labels. Whether they can depends on their
+ # assignee, milestone and labels. Whether they can depends on their
# permissions on the target project.
source_project = @project
@project = Project.find(params[:target_project_id]) if params[:target_project_id]
@@ -18,6 +18,7 @@ module MergeRequests
merge_request.update_attributes(label_ids: label_params)
event_service.open_mr(merge_request, current_user)
notification_service.new_merge_request(merge_request, current_user)
+ todo_service.new_merge_request(merge_request, current_user)
merge_request.create_cross_references!(current_user)
execute_hooks(merge_request)
end
diff --git a/app/services/merge_requests/merge_service.rb b/app/services/merge_requests/merge_service.rb
index e8bef250d8b..9a58383b398 100644
--- a/app/services/merge_requests/merge_service.rb
+++ b/app/services/merge_requests/merge_service.rb
@@ -34,7 +34,8 @@ module MergeRequests
committer: committer
}
- repository.merge(current_user, merge_request.source_sha, merge_request.target_branch, options)
+ commit_id = repository.merge(current_user, merge_request.source_sha, merge_request.target_branch, options)
+ merge_request.update(merge_commit_sha: commit_id)
rescue StandardError => e
merge_request.update(merge_error: "Something went wrong during merge")
Rails.logger.error(e.message)
diff --git a/app/services/merge_requests/merge_when_build_succeeds_service.rb b/app/services/merge_requests/merge_when_build_succeeds_service.rb
index 5cf7404a493..d6af12f9739 100644
--- a/app/services/merge_requests/merge_when_build_succeeds_service.rb
+++ b/app/services/merge_requests/merge_when_build_succeeds_service.rb
@@ -19,15 +19,19 @@ module MergeRequests
end
# Triggers the automatic merge of merge_request once the build succeeds
- def trigger(build)
- merge_requests = merge_request_from(build)
+ def trigger(commit_status)
+ merge_requests = merge_request_from(commit_status)
merge_requests.each do |merge_request|
next unless merge_request.merge_when_build_succeeds?
+ next unless merge_request.mergeable?
- if merge_request.ci_commit && merge_request.ci_commit.success? && merge_request.mergeable?
- MergeWorker.perform_async(merge_request.id, merge_request.merge_user_id, merge_request.merge_params)
- end
+ ci_commit = merge_request.ci_commit
+ next unless ci_commit
+ next unless ci_commit.sha == commit_status.sha
+ next unless ci_commit.success?
+
+ MergeWorker.perform_async(merge_request.id, merge_request.merge_user_id, merge_request.merge_params)
end
end
@@ -45,9 +49,16 @@ module MergeRequests
private
- def merge_request_from(build)
- merge_requests = @project.origin_merge_requests.opened.where(source_branch: build.ref).to_a
- merge_requests += @project.fork_merge_requests.opened.where(source_branch: build.ref).to_a
+ def merge_request_from(commit_status)
+ branches = commit_status.ref
+
+ # This is for ref-less builds
+ branches ||= @project.repository.branch_names_contains(commit_status.sha)
+
+ return [] if branches.blank?
+
+ merge_requests = @project.origin_merge_requests.opened.where(source_branch: branches).to_a
+ merge_requests += @project.fork_merge_requests.opened.where(source_branch: branches).to_a
merge_requests.uniq.select(&:source_project)
end
diff --git a/app/services/merge_requests/update_service.rb b/app/services/merge_requests/update_service.rb
index 5ff2cc03dda..6319ad805b6 100644
--- a/app/services/merge_requests/update_service.rb
+++ b/app/services/merge_requests/update_service.rb
@@ -14,7 +14,16 @@ module MergeRequests
update(merge_request)
end
- def handle_changes(merge_request)
+ def handle_changes(merge_request, options = {})
+ if has_changes?(merge_request, options)
+ todo_service.mark_pending_todos_as_done(merge_request, current_user)
+ end
+
+ if merge_request.previous_changes.include?('title') ||
+ merge_request.previous_changes.include?('description')
+ todo_service.update_merge_request(merge_request, current_user)
+ end
+
if merge_request.previous_changes.include?('target_branch')
create_branch_change_note(merge_request, 'target',
merge_request.previous_changes['target_branch'].first,
@@ -28,6 +37,7 @@ module MergeRequests
if merge_request.previous_changes.include?('assignee_id')
create_assignee_note(merge_request)
notification_service.reassigned_merge_request(merge_request, current_user)
+ todo_service.reassigned_merge_request(merge_request, current_user)
end
if merge_request.previous_changes.include?('target_branch') ||
diff --git a/app/services/notes/create_service.rb b/app/services/notes/create_service.rb
index 8d9661167b5..2bb312bb252 100644
--- a/app/services/notes/create_service.rb
+++ b/app/services/notes/create_service.rb
@@ -8,10 +8,10 @@ module Notes
if note.save
# Finish the harder work in the background
NewNoteWorker.perform_in(2.seconds, note.id, params)
+ TodoService.new.new_note(note, current_user)
end
note
end
-
end
end
diff --git a/app/services/notes/post_process_service.rb b/app/services/notes/post_process_service.rb
index f37d3c50cdd..e818f58d13c 100644
--- a/app/services/notes/post_process_service.rb
+++ b/app/services/notes/post_process_service.rb
@@ -1,6 +1,5 @@
module Notes
class PostProcessService
-
attr_accessor :note
def initialize(note)
@@ -25,6 +24,5 @@ module Notes
@note.project.execute_hooks(note_data, :note_hooks)
@note.project.execute_services(note_data, :note_hooks)
end
-
end
end
diff --git a/app/services/notes/update_service.rb b/app/services/notes/update_service.rb
index 72e2f78008d..1361b1e0300 100644
--- a/app/services/notes/update_service.rb
+++ b/app/services/notes/update_service.rb
@@ -7,6 +7,10 @@ module Notes
note.create_new_cross_references!(current_user)
note.reset_events_cache
+ if note.previous_changes.include?('note')
+ TodoService.new.update_note(note, current_user)
+ end
+
note
end
end
diff --git a/app/services/projects/destroy_service.rb b/app/services/projects/destroy_service.rb
index 294157b4f0e..df5054f08d7 100644
--- a/app/services/projects/destroy_service.rb
+++ b/app/services/projects/destroy_service.rb
@@ -16,11 +16,15 @@ module Projects
return false unless can?(current_user, :remove_project, project)
project.team.truncate
- project.repository.expire_cache unless project.empty_repo?
repo_path = project.path_with_namespace
wiki_path = repo_path + '.wiki'
+ # Flush the cache for both repositories. This has to be done _before_
+ # removing the physical repositories as some expiration code depends on
+ # Git data (e.g. a list of branch names).
+ flush_caches(project, wiki_path)
+
Project.transaction do
project.destroy!
@@ -70,5 +74,11 @@ module Projects
def removal_path(path)
"#{path}+#{project.id}#{DELETED_FLAG}"
end
+
+ def flush_caches(project, wiki_path)
+ project.repository.before_delete
+
+ Repository.new(wiki_path, project).before_delete
+ end
end
end
diff --git a/app/services/system_note_service.rb b/app/services/system_note_service.rb
index 1083bcec054..58a861ee08e 100644
--- a/app/services/system_note_service.rb
+++ b/app/services/system_note_service.rb
@@ -66,7 +66,7 @@ class SystemNoteService
def self.change_label(noteable, project, author, added_labels, removed_labels)
labels_count = added_labels.count + removed_labels.count
- references = ->(label) { label.to_reference(:id) }
+ references = ->(label) { label.to_reference(format: :id) }
added_labels = added_labels.map(&references).join(' ')
removed_labels = removed_labels.map(&references).join(' ')
@@ -274,12 +274,15 @@ class SystemNoteService
# Check if a cross reference to a noteable from a mentioner already exists
#
# This method is used to prevent multiple notes being created for a mention
- # when a issue is updated, for example.
+ # when a issue is updated, for example. The method also calls notes_for_mentioner
+ # to check if the mentioner is a commit, and return matches only on commit hash
+ # instead of project + commit, to avoid repeated mentions from forks.
#
# noteable - Noteable object being referenced
# mentioner - Mentionable object
#
# Returns Boolean
+
def self.cross_reference_exists?(noteable, mentioner)
# Initial scope should be system notes of this noteable type
notes = Note.system.where(noteable_type: noteable.class)
@@ -291,14 +294,20 @@ class SystemNoteService
notes = notes.where(noteable_id: noteable.id)
end
- gfm_reference = mentioner.gfm_reference(noteable.project)
- notes = notes.where(note: cross_reference_note_content(gfm_reference))
-
- notes.count > 0
+ notes_for_mentioner(mentioner, noteable, notes).count > 0
end
private
+ def self.notes_for_mentioner(mentioner, noteable, notes)
+ if mentioner.is_a?(Commit)
+ notes.where('note LIKE ?', "#{cross_reference_note_prefix}%#{mentioner.to_reference(nil)}")
+ else
+ gfm_reference = mentioner.gfm_reference(noteable.project)
+ notes.where(note: cross_reference_note_content(gfm_reference))
+ end
+ end
+
def self.create_note(args = {})
Note.create(args.merge(system: true))
end
diff --git a/app/services/todo_service.rb b/app/services/todo_service.rb
new file mode 100644
index 00000000000..4392e2d17fe
--- /dev/null
+++ b/app/services/todo_service.rb
@@ -0,0 +1,170 @@
+# TodoService class
+#
+# Used for creating todos after certain user actions
+#
+# Ex.
+# TodoService.new.new_issue(issue, current_user)
+#
+class TodoService
+ # When create an issue we should:
+ #
+ # * create a todo for assignee if issue is assigned
+ # * create a todo for each mentioned user on issue
+ #
+ def new_issue(issue, current_user)
+ new_issuable(issue, current_user)
+ end
+
+ # When update an issue we should:
+ #
+ # * mark all pending todos related to the issue for the current user as done
+ #
+ def update_issue(issue, current_user)
+ create_mention_todos(issue.project, issue, current_user)
+ end
+
+ # When close an issue we should:
+ #
+ # * mark all pending todos related to the target for the current user as done
+ #
+ def close_issue(issue, current_user)
+ mark_pending_todos_as_done(issue, current_user)
+ end
+
+ # When we reassign an issue we should:
+ #
+ # * create a pending todo for new assignee if issue is assigned
+ #
+ def reassigned_issue(issue, current_user)
+ create_assignment_todo(issue, current_user)
+ end
+
+ # When create a merge request we should:
+ #
+ # * creates a pending todo for assignee if merge request is assigned
+ # * create a todo for each mentioned user on merge request
+ #
+ def new_merge_request(merge_request, current_user)
+ new_issuable(merge_request, current_user)
+ end
+
+ # When update a merge request we should:
+ #
+ # * create a todo for each mentioned user on merge request
+ #
+ def update_merge_request(merge_request, current_user)
+ create_mention_todos(merge_request.project, merge_request, current_user)
+ end
+
+ # When close a merge request we should:
+ #
+ # * mark all pending todos related to the target for the current user as done
+ #
+ def close_merge_request(merge_request, current_user)
+ mark_pending_todos_as_done(merge_request, current_user)
+ end
+
+ # When we reassign a merge request we should:
+ #
+ # * creates a pending todo for new assignee if merge request is assigned
+ #
+ def reassigned_merge_request(merge_request, current_user)
+ create_assignment_todo(merge_request, current_user)
+ end
+
+ # When merge a merge request we should:
+ #
+ # * mark all pending todos related to the target for the current user as done
+ #
+ def merge_merge_request(merge_request, current_user)
+ mark_pending_todos_as_done(merge_request, current_user)
+ end
+
+ # When create a note we should:
+ #
+ # * mark all pending todos related to the noteable for the note author as done
+ # * create a todo for each mentioned user on note
+ #
+ def new_note(note, current_user)
+ handle_note(note, current_user)
+ end
+
+ # When update a note we should:
+ #
+ # * mark all pending todos related to the noteable for the current user as done
+ # * create a todo for each new user mentioned on note
+ #
+ def update_note(note, current_user)
+ handle_note(note, current_user)
+ end
+
+ # When marking pending todos as done we should:
+ #
+ # * mark all pending todos related to the target for the current user as done
+ #
+ def mark_pending_todos_as_done(target, user)
+ pending_todos(user, target.project, target).update_all(state: :done)
+ end
+
+ private
+
+ def create_todos(project, target, author, users, action, note = nil)
+ Array(users).each do |user|
+ next if pending_todos(user, project, target).exists?
+
+ Todo.create(
+ project: project,
+ user_id: user.id,
+ author_id: author.id,
+ target_id: target.id,
+ target_type: target.class.name,
+ action: action,
+ note: note
+ )
+ end
+ end
+
+ def new_issuable(issuable, author)
+ create_assignment_todo(issuable, author)
+ create_mention_todos(issuable.project, issuable, author)
+ end
+
+ def handle_note(note, author)
+ # Skip system notes, notes on commit, and notes on project snippet
+ return if note.system? || ['Commit', 'Snippet'].include?(note.noteable_type)
+
+ project = note.project
+ target = note.noteable
+
+ mark_pending_todos_as_done(target, author)
+ create_mention_todos(project, target, author, note)
+ end
+
+ def create_assignment_todo(issuable, author)
+ if issuable.assignee && issuable.assignee != author
+ create_todos(issuable.project, issuable, author, issuable.assignee, Todo::ASSIGNED)
+ end
+ end
+
+ def create_mention_todos(project, issuable, author, note = nil)
+ mentioned_users = filter_mentioned_users(project, note || issuable, author)
+ create_todos(project, issuable, author, mentioned_users, Todo::MENTIONED, note)
+ end
+
+ def filter_mentioned_users(project, target, author)
+ mentioned_users = target.mentioned_users.select do |user|
+ user.can?(:read_project, project)
+ end
+
+ mentioned_users.delete(author)
+ mentioned_users.uniq
+ end
+
+ def pending_todos(user, project, target)
+ user.todos.pending.where(
+ project_id: project.id,
+ target_id: target.id,
+ target_type: target.class.name
+ )
+ end
+end
diff --git a/app/uploaders/avatar_uploader.rb b/app/uploaders/avatar_uploader.rb
index 6135c3ad96f..2c72df44ff0 100644
--- a/app/uploaders/avatar_uploader.rb
+++ b/app/uploaders/avatar_uploader.rb
@@ -2,11 +2,22 @@
class AvatarUploader < CarrierWave::Uploader::Base
include UploaderHelper
+ include CarrierWave::MiniMagick
storage :file
after :store, :reset_events_cache
+ process :cropper
+
+ def cropper
+ return unless model.respond_to?(:avatar_crop_size) && model.valid?
+
+ manipulate! do |img|
+ img.crop "#{model.avatar_crop_size}x#{model.avatar_crop_size}+#{model.avatar_crop_x}+#{model.avatar_crop_y}"
+ end
+ end
+
def store_dir
"uploads/#{model.class.to_s.underscore}/#{mounted_as}/#{model.id}"
end
diff --git a/app/validators/url_validator.rb b/app/validators/url_validator.rb
index 2848b9cd33d..a77beb2683d 100644
--- a/app/validators/url_validator.rb
+++ b/app/validators/url_validator.rb
@@ -29,8 +29,11 @@ class UrlValidator < ActiveModel::EachValidator
end
def valid_url?(value)
+ return false if value.nil?
+
options = default_options.merge(self.options)
+ value.strip!
value =~ /\A#{URI.regexp(options[:protocols])}\z/
end
end
diff --git a/app/views/abuse_reports/new.html.haml b/app/views/abuse_reports/new.html.haml
index f125ecf7be5..3bc1b24b5e2 100644
--- a/app/views/abuse_reports/new.html.haml
+++ b/app/views/abuse_reports/new.html.haml
@@ -2,7 +2,7 @@
%h3.page-title Report abuse
%p Please use this form to report users who create spam issues, comments or behave inappropriately.
%hr
-= form_for @abuse_report, html: { class: 'form-horizontal js-requires-input'} do |f|
+= form_for @abuse_report, html: { class: 'form-horizontal js-quick-submit js-requires-input'} do |f|
= f.hidden_field :user_id
- if @abuse_report.errors.any?
.alert.alert-danger
@@ -16,7 +16,7 @@
.form-group
= f.label :message, class: 'control-label'
.col-sm-10
- = f.text_area :message, class: "form-control js-quick-submit", rows: 2, required: true, value: sanitize(@ref_url)
+ = f.text_area :message, class: "form-control", rows: 2, required: true, value: sanitize(@ref_url)
.help-block
Explain the problem with this user. If appropriate, provide a link to the relevant issue or comment.
diff --git a/app/views/admin/appearances/_form.html.haml b/app/views/admin/appearances/_form.html.haml
new file mode 100644
index 00000000000..6f325914d14
--- /dev/null
+++ b/app/views/admin/appearances/_form.html.haml
@@ -0,0 +1,58 @@
+= form_for @appearance, url: admin_appearances_path, html: { class: 'form-horizontal'} do |f|
+ - if @appearance.errors.any?
+ .alert.alert-danger
+ - @appearance.errors.full_messages.each do |msg|
+ %p= msg
+
+ %fieldset.sign-in
+ %legend
+ Sign in/Sign up pages:
+ .form-group
+ = f.label :title, class: 'control-label'
+ .col-sm-10
+ = f.text_field :title, class: "form-control"
+ .form-group
+ = f.label :description, class: 'control-label'
+ .col-sm-10
+ = f.text_area :description, class: "form-control", rows: 10
+ .hint
+ Description parsed with #{link_to "GitLab Flavored Markdown", help_page_path('markdown', 'markdown'), target: '_blank'}.
+ .form-group
+ = f.label :logo, class: 'control-label'
+ .col-sm-10
+ - if @appearance.logo?
+ = image_tag @appearance.logo_url, class: 'appearance-logo-preview'
+ - if @appearance.persisted?
+ %br
+ = link_to 'Remove logo', logo_admin_appearances_path, data: { confirm: "Logo will be removed. Are you sure?"}, method: :delete, class: "btn btn-remove btn-small remove-logo"
+ %hr
+ = f.hidden_field :logo_cache
+ = f.file_field :logo, class: ""
+ .hint
+ Maximum file size is 1MB. Pages are optimized for a 640x360 px logo.
+
+ %fieldset.app_logo
+ %legend
+ Navigation bar:
+ .form-group
+ = f.label :header_logo, 'Header logo', class: 'control-label'
+ .col-sm-10
+ - if @appearance.header_logo?
+ = image_tag @appearance.header_logo_url, class: 'appearance-light-logo-preview'
+ - if @appearance.persisted?
+ %br
+ = link_to 'Remove header logo', header_logos_admin_appearances_path, data: { confirm: "Header logo will be removed. Are you sure?"}, method: :delete, class: "btn btn-remove btn-small remove-logo"
+ %hr
+ = f.hidden_field :header_logo_cache
+ = f.file_field :header_logo, class: ""
+ .hint
+ Maximum file size is 1MB. Pages are optimized for a 72x72 px header logo
+
+ .form-actions
+ = f.submit 'Save', class: 'btn btn-save'
+ - if @appearance.persisted?
+ = link_to 'Preview last save', preview_admin_appearances_path, class: 'btn', target: '_blank'
+
+ - if @appearance.updated_at
+ %span.pull-right
+ Last edit #{time_ago_with_tooltip(@appearance.updated_at)}
diff --git a/app/views/admin/appearances/preview.html.haml b/app/views/admin/appearances/preview.html.haml
new file mode 100644
index 00000000000..dd4a64e80bc
--- /dev/null
+++ b/app/views/admin/appearances/preview.html.haml
@@ -0,0 +1,29 @@
+- page_title "Preview | Appearance"
+%h3.page-title
+ Appearance settings - Preview
+%hr
+
+.ui-box
+ .title
+ Sign-in page
+ %div
+ .login-page
+ .container
+ .content
+ .login-title
+ %h1= brand_title
+ %hr
+ .container
+ .content
+ .row
+ .col-sm-7
+ .brand-image
+ = brand_image
+ .brand_text
+ = brand_text
+ .col-sm-4
+ .login-box
+ %h3.page-title Sign in
+ = text_field_tag :login, nil, class: "form-control top", placeholder: "Username or Email"
+ = password_field_tag :password, nil, class: "form-control bottom", placeholder: "Password"
+ = button_tag "Sign in", class: "btn-create btn"
diff --git a/app/views/admin/appearances/show.html.haml b/app/views/admin/appearances/show.html.haml
new file mode 100644
index 00000000000..089e8e4cb7a
--- /dev/null
+++ b/app/views/admin/appearances/show.html.haml
@@ -0,0 +1,7 @@
+- page_title "Appearance"
+%h3.page-title
+ Appearance settings
+%p.light
+ You can modify the look and feel of GitLab here
+
+= render 'form'
diff --git a/app/views/admin/broadcast_messages/_form.html.haml b/app/views/admin/broadcast_messages/_form.html.haml
index 5c9403fa0c2..b748460a9f7 100644
--- a/app/views/admin/broadcast_messages/_form.html.haml
+++ b/app/views/admin/broadcast_messages/_form.html.haml
@@ -3,7 +3,7 @@
.js-broadcast-message-preview
= render_broadcast_message(@broadcast_message.message.presence || "Your message here")
-= form_for [:admin, @broadcast_message], html: { class: 'broadcast-message-form form-horizontal js-requires-input'} do |f|
+= form_for [:admin, @broadcast_message], html: { class: 'broadcast-message-form form-horizontal js-quick-submit js-requires-input'} do |f|
-if @broadcast_message.errors.any?
.alert.alert-danger
- @broadcast_message.errors.full_messages.each do |msg|
@@ -11,7 +11,7 @@
.form-group
= f.label :message, class: 'control-label'
.col-sm-10
- = f.text_area :message, class: "form-control js-quick-submit js-autosize",
+ = f.text_area :message, class: "form-control js-autosize",
required: true,
data: { preview_path: preview_admin_broadcast_messages_path }
.form-group.js-toggle-colors-container
diff --git a/app/views/admin/labels/_form.html.haml b/app/views/admin/labels/_form.html.haml
index eaa94ed9e36..8c6b389bf15 100644
--- a/app/views/admin/labels/_form.html.haml
+++ b/app/views/admin/labels/_form.html.haml
@@ -12,6 +12,10 @@
.col-sm-10
= f.text_field :title, class: "form-control", required: true
.form-group
+ = f.label :description, class: 'control-label'
+ .col-sm-10
+ = f.text_field :description, class: "form-control js-quick-submit"
+ .form-group
= f.label :color, "Background color", class: 'control-label'
.col-sm-10
.input-group
diff --git a/app/views/admin/labels/_label.html.haml b/app/views/admin/labels/_label.html.haml
index e3ccbf6c3a8..5736a301910 100644
--- a/app/views/admin/labels/_label.html.haml
+++ b/app/views/admin/labels/_label.html.haml
@@ -1,5 +1,7 @@
%li{id: dom_id(label)}
- = render_colored_label(label)
- .pull-right
- = link_to 'Edit', edit_admin_label_path(label), class: 'btn btn-sm'
- = link_to 'Delete', admin_label_path(label), class: 'btn btn-sm btn-remove remove-row', method: :delete, remote: true, data: {confirm: "Delete this label? Are you sure?"}
+ .label-row
+ = render_colored_label(label)
+ = markdown(label.description, pipeline: :single_line)
+ .pull-right
+ = link_to 'Edit', edit_admin_label_path(label), class: 'btn btn-sm'
+ = link_to 'Delete', admin_label_path(label), class: 'btn btn-sm btn-remove remove-row', method: :delete, remote: true, data: {confirm: "Delete this label? Are you sure?"}
diff --git a/app/views/admin/users/keys.html.haml b/app/views/admin/users/keys.html.haml
index 07110717082..0f644121e62 100644
--- a/app/views/admin/users/keys.html.haml
+++ b/app/views/admin/users/keys.html.haml
@@ -1,3 +1,3 @@
-- page_title "Keys", @user.name, "Users"
+- page_title "SSH Keys", @user.name, "Users"
= render 'admin/users/head'
= render 'profiles/keys/key_table', admin: true
diff --git a/app/views/dashboard/_projects_head.html.haml b/app/views/dashboard/_projects_head.html.haml
index 4bc761b3738..40f88261c10 100644
--- a/app/views/dashboard/_projects_head.html.haml
+++ b/app/views/dashboard/_projects_head.html.haml
@@ -14,7 +14,7 @@
.nav-controls
= form_tag request.original_url, method: :get, class: 'project-filter-form', id: 'project-filter-form' do |f|
- = search_field_tag :filter_projects, params[:filter_projects], placeholder: 'Filter by name...', class: 'project-filter-form-field form-control input-short projects-list-filter', spellcheck: false, id: 'project-filter-form-field'
+ = search_field_tag :filter_projects, params[:filter_projects], placeholder: 'Filter by name...', class: 'project-filter-form-field form-control input-short projects-list-filter', spellcheck: false, id: 'project-filter-form-field', tabindex: "2"
= render 'explore/projects/dropdown'
- if current_user.can_create_project?
= link_to new_project_path, class: 'btn btn-new' do
diff --git a/app/views/dashboard/milestones/_issue.html.haml b/app/views/dashboard/milestones/_issue.html.haml
deleted file mode 100644
index 1408ebdd5dc..00000000000
--- a/app/views/dashboard/milestones/_issue.html.haml
+++ /dev/null
@@ -1,10 +0,0 @@
-%li{ id: dom_id(issue, 'sortable'), class: 'issue-row', 'data-iid' => issue.iid }
- %span.milestone-row
- - project = issue.project
- %strong #{project.name_with_namespace} &middot;
- = link_to [project.namespace.becomes(Namespace), project, issue] do
- %span.cgray ##{issue.iid}
- = link_to_gfm issue.title, [project.namespace.becomes(Namespace), project, issue], title: issue.title
- .pull-right.assignee-icon
- - if issue.assignee
- = image_tag avatar_icon(issue.assignee, 16), class: "avatar s16"
diff --git a/app/views/dashboard/milestones/_issues.html.haml b/app/views/dashboard/milestones/_issues.html.haml
deleted file mode 100644
index 9f350b772bd..00000000000
--- a/app/views/dashboard/milestones/_issues.html.haml
+++ /dev/null
@@ -1,6 +0,0 @@
-.panel.panel-default
- .panel-heading= title
- %ul{ class: "well-list issues-sortable-list" }
- - if issues
- - issues.each do |issue|
- = render 'issue', issue: issue
diff --git a/app/views/dashboard/milestones/_merge_request.html.haml b/app/views/dashboard/milestones/_merge_request.html.haml
deleted file mode 100644
index 77c46de030b..00000000000
--- a/app/views/dashboard/milestones/_merge_request.html.haml
+++ /dev/null
@@ -1,10 +0,0 @@
-%li{ id: dom_id(merge_request, 'sortable'), class: 'mr-row', 'data-iid' => merge_request.iid }
- %span.milestone-row
- - project = merge_request.project
- %strong #{project.name_with_namespace} &middot;
- = link_to [project.namespace.becomes(Namespace), project, merge_request] do
- %span.cgray ##{merge_request.iid}
- = link_to_gfm merge_request.title, [project.namespace.becomes(Namespace), project, merge_request], title: merge_request.title
- .pull-right.assignee-icon
- - if merge_request.assignee
- = image_tag avatar_icon(merge_request.assignee, 16), class: "avatar s16"
diff --git a/app/views/dashboard/milestones/_merge_requests.html.haml b/app/views/dashboard/milestones/_merge_requests.html.haml
deleted file mode 100644
index 50057e2c636..00000000000
--- a/app/views/dashboard/milestones/_merge_requests.html.haml
+++ /dev/null
@@ -1,6 +0,0 @@
-.panel.panel-default
- .panel-heading= title
- %ul{ class: "well-list merge_requests-sortable-list" }
- - if merge_requests
- - merge_requests.each do |merge_request|
- = render 'merge_request', merge_request: merge_request
diff --git a/app/views/dashboard/milestones/_milestone.html.haml b/app/views/dashboard/milestones/_milestone.html.haml
index 7c882a32702..6173ca6ab9b 100644
--- a/app/views/dashboard/milestones/_milestone.html.haml
+++ b/app/views/dashboard/milestones/_milestone.html.haml
@@ -1,25 +1,6 @@
-%li{class: "milestone milestone-#{milestone.closed? ? 'closed' : 'open'}", id: dom_id(milestone.milestones.first) }
- .row
- .col-sm-6
- %strong
- = link_to_gfm truncate(milestone.title, length: 100), dashboard_milestone_path(milestone.safe_title, title: milestone.title)
- .col-sm-6
- .pull-right.light #{milestone.percent_complete}% complete
- .row
- .col-sm-6
- = link_to issues_dashboard_path(milestone_title: milestone.title) do
- = pluralize milestone.issue_count, 'Issue'
- &middot;
- = link_to merge_requests_dashboard_path(milestone_title: milestone.title) do
- = pluralize milestone.merge_requests_count, 'Merge Request'
- .col-sm-6
- = milestone_progress_bar(milestone)
- .row
- .col-sm-6
- .expiration
- = render 'shared/milestone_expired', milestone: milestone
- .projects
- - milestone.milestones.each do |milestone|
- = link_to milestone_path(milestone) do
- %span.label.label-gray
- = milestone.project.name_with_namespace
+= render 'shared/milestones/milestone',
+ milestone_path: dashboard_milestone_path(milestone.safe_title, title: milestone.title),
+ issues_path: issues_dashboard_path(milestone_title: milestone.title),
+ merge_requests_path: merge_requests_dashboard_path(milestone_title: milestone.title),
+ milestone: milestone,
+ dashboard: true
diff --git a/app/views/dashboard/milestones/show.html.haml b/app/views/dashboard/milestones/show.html.haml
index 3810267577c..60c84a26420 100644
--- a/app/views/dashboard/milestones/show.html.haml
+++ b/app/views/dashboard/milestones/show.html.haml
@@ -1,105 +1,5 @@
-- page_title @milestone.title, "Milestones"
- header_title "Milestones", dashboard_milestones_path
-.detail-page-header
- .status-box{ class: "status-box-#{@milestone.closed? ? 'closed' : 'open'}" }
- - if @milestone.closed?
- Closed
- - else
- Open
- %span.identifier
- Milestone #{@milestone.title}
-
-.detail-page-description.gray-content-block.second-block
- %h2.title
- = markdown escape_once(@milestone.title), pipeline: :single_line
-
-- if @milestone.complete? && @milestone.active?
- .alert.alert-success.prepend-top-default
- %span All issues for this milestone are closed. Navigate to the project to close the milestone.
-
-.table-holder
- %table.table
- %thead
- %tr
- %th Project
- %th Open issues
- %th State
- %th Due date
- - @milestone.milestones.each do |milestone|
- %tr
- %td
- = link_to "#{milestone.project.name_with_namespace}", namespace_project_milestone_path(milestone.project.namespace, milestone.project, milestone)
- %td
- = milestone.issues.opened.count
- %td
- - if milestone.closed?
- Closed
- - else
- Open
- %td
- = milestone.expires_at
-
-.context
- %p.lead
- Progress:
- #{@milestone.closed_items_count} closed
- &ndash;
- #{@milestone.open_items_count} open
- = milestone_progress_bar(@milestone)
-
-%ul.nav-links.no-top.no-bottom
- %li.active
- = link_to '#tab-issues', 'data-toggle' => 'tab' do
- Issues
- %span.badge= @milestone.issue_count
- %li
- = link_to '#tab-merge-requests', 'data-toggle' => 'tab' do
- Merge Requests
- %span.badge= @milestone.merge_requests_count
- %li
- = link_to '#tab-participants', 'data-toggle' => 'tab' do
- Participants
- %span.badge= @milestone.participants.count
-
-.tab-content
- .tab-pane.active#tab-issues
- .gray-content-block.middle-block
- .pull-right
- = link_to 'Browse Issues', issues_dashboard_path(milestone_title: @milestone.title), class: "btn btn-grouped"
-
- .oneline
- All issues in this milestone
-
- .row.prepend-top-default
- .col-md-6
- = render 'issues', title: "Open", issues: @milestone.opened_issues
- .col-md-6
- = render 'issues', title: "Closed", issues: @milestone.closed_issues
-
- .tab-pane#tab-merge-requests
- .gray-content-block.middle-block
- .pull-right
- = link_to 'Browse Merge Requests', merge_requests_dashboard_path(milestone_title: @milestone.title), class: "btn btn-grouped"
-
- .oneline
- All merge requests in this milestone
-
- .row.prepend-top-default
- .col-md-6
- = render 'merge_requests', title: "Open", merge_requests: @milestone.opened_merge_requests
- .col-md-6
- = render 'merge_requests', title: "Closed", merge_requests: @milestone.closed_merge_requests
-
- .tab-pane#tab-participants
- .gray-content-block.middle-block
- .oneline
- All participants to this milestone
- %ul.bordered-list
- - @milestone.participants.each do |user|
- %li
- = link_to user, title: user.name, class: "darken" do
- = image_tag avatar_icon(user, 32), class: "avatar s32"
- %strong= truncate(user.name, lenght: 40)
- %br
- %small.cgray= user.username
+= render 'shared/milestones/top', milestone: @milestone
+= render 'shared/milestones/summary', milestone: @milestone
+= render 'shared/milestones/tabs', milestone: @milestone, show_full_project_name: true
diff --git a/app/views/dashboard/projects/_projects.html.haml b/app/views/dashboard/projects/_projects.html.haml
index 933a3edd0f0..0ebd7c01bab 100644
--- a/app/views/dashboard/projects/_projects.html.haml
+++ b/app/views/dashboard/projects/_projects.html.haml
@@ -1,6 +1 @@
-.projects-list-holder
-
- = render 'shared/projects/list', projects: @projects, ci: true
-
- :javascript
- Dashboard.init()
+= render 'shared/projects/list', projects: @projects, ci: true
diff --git a/app/views/dashboard/projects/_zero_authorized_projects.html.haml b/app/views/dashboard/projects/_zero_authorized_projects.html.haml
index 4e7d6639727..c3efa7727b1 100644
--- a/app/views/dashboard/projects/_zero_authorized_projects.html.haml
+++ b/app/views/dashboard/projects/_zero_authorized_projects.html.haml
@@ -11,7 +11,7 @@
%br
- if current_user.can_create_project?
You can create up to
- %strong= pluralize(current_user.projects_limit, "project") + "."
+ %strong= pluralize(number_with_delimiter(current_user.projects_limit), "project") + "."
- else
If you are added to a project, it will be displayed here.
@@ -44,7 +44,7 @@
.dashboard-intro-text
%p.slead
There are
- %strong= publicish_project_count
+ %strong= number_with_delimiter(publicish_project_count)
public projects on this server.
%br
Public projects are an easy way to allow everyone to have read-only access.
diff --git a/app/views/dashboard/projects/index.html.haml b/app/views/dashboard/projects/index.html.haml
index 53abf274bdb..4565e752c1f 100644
--- a/app/views/dashboard/projects/index.html.haml
+++ b/app/views/dashboard/projects/index.html.haml
@@ -10,7 +10,7 @@
- if @last_push
= render "events/event_last_push", event: @last_push
-- if @projects.any?
+- if @projects.any? || params[:filter_projects]
= render 'projects'
- else
= render "zero_authorized_projects"
diff --git a/app/views/dashboard/todos/_todo.html.haml b/app/views/dashboard/todos/_todo.html.haml
new file mode 100644
index 00000000000..f878d36e739
--- /dev/null
+++ b/app/views/dashboard/todos/_todo.html.haml
@@ -0,0 +1,21 @@
+%li{class: "todo todo-#{todo.done? ? 'done' : 'pending'}", id: dom_id(todo) }
+ .todo-item.todo-block
+ = image_tag avatar_icon(todo.author_email, 40), class: 'avatar s40', alt:''
+
+ .todo-title
+ %span.author-name
+ = link_to_author todo
+ %span.todo-label
+ = todo_action_name(todo)
+ = todo_target_link(todo)
+
+ &middot; #{time_ago_with_tooltip(todo.created_at)}
+
+ - if todo.pending?
+ .todo-actions.pull-right
+ = link_to 'Done', [:dashboard, todo], method: :delete, class: 'btn'
+
+ .todo-body
+ .todo-note
+ .md
+ = event_note(todo.body, project: todo.project)
diff --git a/app/views/dashboard/todos/index.html.haml b/app/views/dashboard/todos/index.html.haml
new file mode 100644
index 00000000000..946d7df3933
--- /dev/null
+++ b/app/views/dashboard/todos/index.html.haml
@@ -0,0 +1,62 @@
+- page_title "Todos"
+- header_title "Todos", dashboard_todos_path
+
+.top-area
+ %ul.nav-links
+ %li{class: ('active' if params[:state].blank? || params[:state] == 'pending')}
+ = link_to todos_filter_path(state: 'pending') do
+ %span
+ To do
+ %span{class: 'badge'}
+ = todos_pending_count
+ %li{class: ('active' if params[:state] == 'done')}
+ = link_to todos_filter_path(state: 'done') do
+ %span
+ Done
+ %span{class: 'badge'}
+ = todos_done_count
+
+ .nav-controls
+ - if @todos.any?(&:pending?)
+ = link_to 'Mark all as done', destroy_all_dashboard_todos_path(todos_filter_params), class: 'btn', method: :delete
+
+.todos-filters
+ .gray-content-block.second-block
+ = form_tag todos_filter_path(without: [:project_id, :author_id, :type, :action_id]), method: :get, class: 'filter-form' do
+ .filter-item.inline
+ = select_tag('project_id', todo_projects_options,
+ class: 'select2 trigger-submit', include_blank: true,
+ data: {placeholder: 'Project'})
+ .filter-item.inline
+ = users_select_tag(:author_id, selected: params[:author_id],
+ placeholder: 'Author', class: 'trigger-submit', any_user: "Any Author", first_user: true, current_user: true)
+ .filter-item.inline
+ = select_tag('type', todo_types_options,
+ class: 'select2 trigger-submit', include_blank: true,
+ data: {placeholder: 'Type'})
+ .filter-item.inline.actions-filter
+ = select_tag('action_id', todo_actions_options,
+ class: 'select2 trigger-submit', include_blank: true,
+ data: {placeholder: 'Action'})
+
+.prepend-top-default
+ - if @todos.any?
+ - @todos.group_by(&:project).each do |group|
+ .panel.panel-default.panel-small
+ - project = group[0]
+ .panel-heading
+ = link_to project.name_with_namespace, namespace_project_path(project.namespace, project)
+
+ %ul.well-list.todos-list
+ = render group[1]
+ = paginate @todos, theme: "gitlab"
+ - else
+ .nothing-here-block You're all done!
+
+:javascript
+ new UsersSelect();
+
+ $('form.filter-form').on('submit', function (event) {
+ event.preventDefault();
+ Turbolinks.visit(this.action + '&' + $(this).serialize());
+ });
diff --git a/app/views/devise/sessions/new.html.haml b/app/views/devise/sessions/new.html.haml
index dbc8eda6196..d65fa60025c 100644
--- a/app/views/devise/sessions/new.html.haml
+++ b/app/views/devise/sessions/new.html.haml
@@ -1,10 +1,10 @@
- page_title "Sign in"
%div
- - if signin_enabled? || ldap_enabled?
+ - if signin_enabled? || ldap_enabled? || crowd_enabled?
= render 'devise/shared/signin_box'
-# Omniauth fits between signin/ldap signin and signup and does not have a surrounding box
- - if Gitlab.config.omniauth.enabled && devise_mapping.omniauthable?
+ - if omniauth_enabled? && devise_mapping.omniauthable?
.clearfix.prepend-top-20
= render 'devise/shared/omniauth_box'
@@ -14,6 +14,6 @@
= render 'devise/shared/signup_box'
-# Show a message if none of the mechanisms above are enabled
- - if !signin_enabled? && !ldap_enabled? && !(Gitlab.config.omniauth.enabled && devise_mapping.omniauthable?)
+ - if !signin_enabled? && !ldap_enabled? && !(omniauth_enabled? && devise_mapping.omniauthable?)
%div
No authentication methods configured.
diff --git a/app/views/emojis/index.html.haml b/app/views/emojis/index.html.haml
new file mode 100644
index 00000000000..b66e513e4d2
--- /dev/null
+++ b/app/views/emojis/index.html.haml
@@ -0,0 +1,9 @@
+.emoji-menu
+ .emoji-menu-content
+ = text_field_tag :emoji_search, "", class: "emoji-search search-input form-control"
+ - AwardEmoji.emoji_by_category.each do |category, emojis|
+ %h5= AwardEmoji::CATEGORIES[category]
+ %ul
+ - emojis.each do |emoji|
+ %li
+ = emoji_icon(emoji["name"], emoji["unicode"], emoji["aliases"]) \ No newline at end of file
diff --git a/app/views/events/event/_common.html.haml b/app/views/events/event/_common.html.haml
index 4ecf1c33d2a..e9e16a7646f 100644
--- a/app/views/events/event/_common.html.haml
+++ b/app/views/events/event/_common.html.haml
@@ -4,7 +4,7 @@
= event_action_name(event)
- if event.target
- %strong= link_to "##{event.target_iid}", [event.project.namespace.becomes(Namespace), event.project, event.target]
+ %strong= link_to event.target.to_reference, [event.project.namespace.becomes(Namespace), event.project, event.target]
= event_preposition(event)
diff --git a/app/views/explore/projects/_dropdown.html.haml b/app/views/explore/projects/_dropdown.html.haml
index 87c556adc7d..a4b4cd8d6c7 100644
--- a/app/views/explore/projects/_dropdown.html.haml
+++ b/app/views/explore/projects/_dropdown.html.haml
@@ -6,7 +6,7 @@
- else
= sort_title_recently_updated
%b.caret
- %ul.dropdown-menu
+ %ul.dropdown-menu.dropdown-menu-align-right
%li
= link_to explore_projects_filter_path(sort: sort_value_name) do
= sort_title_name
diff --git a/app/views/explore/projects/_filter.html.haml b/app/views/explore/projects/_filter.html.haml
index c248dbb695f..39e3e8e2738 100644
--- a/app/views/explore/projects/_filter.html.haml
+++ b/app/views/explore/projects/_filter.html.haml
@@ -1,41 +1,40 @@
-.pull-right.hidden-sm.hidden-xs
- - if current_user
- .dropdown.inline.append-right-10
- %a.dropdown-toggle.btn{href: '#', "data-toggle" => "dropdown"}
- %i.fa.fa-globe
- %span.light Visibility:
- - if params[:visibility_level].present?
- = visibility_level_label(params[:visibility_level].to_i)
- - else
+- if current_user
+ .dropdown
+ %a.dropdown-toggle.btn{href: '#', "data-toggle" => "dropdown"}
+ = icon('globe')
+ %span.light Visibility:
+ - if params[:visibility_level].present?
+ = visibility_level_label(params[:visibility_level].to_i)
+ - else
+ Any
+ %b.caret
+ %ul.dropdown-menu
+ %li
+ = link_to explore_projects_filter_path(visibility_level: nil) do
Any
- %b.caret
- %ul.dropdown-menu
- %li
- = link_to explore_projects_filter_path(visibility_level: nil) do
- Any
- - Gitlab::VisibilityLevel.values.each do |level|
- %li{ class: (level.to_s == params[:visibility_level]) ? 'active' : 'light' }
- = link_to explore_projects_filter_path(visibility_level: level) do
- = visibility_level_icon(level)
- = visibility_level_label(level)
+ - Gitlab::VisibilityLevel.values.each do |level|
+ %li{ class: (level.to_s == params[:visibility_level]) ? 'active' : 'light' }
+ = link_to explore_projects_filter_path(visibility_level: level) do
+ = visibility_level_icon(level)
+ = visibility_level_label(level)
- - if @tags.present?
- .dropdown.inline.append-right-10
- %a.dropdown-toggle.btn{href: '#', "data-toggle" => "dropdown"}
- %i.fa.fa-tags
- %span.light Tags:
- - if params[:tag].present?
- = params[:tag]
- - else
+- if @tags.present?
+ .dropdown
+ %a.dropdown-toggle.btn{href: '#', "data-toggle" => "dropdown"}
+ = icon('tags')
+ %span.light Tags:
+ - if params[:tag].present?
+ = params[:tag]
+ - else
+ Any
+ %b.caret
+ %ul.dropdown-menu
+ %li
+ = link_to explore_projects_filter_path(tag: nil) do
Any
- %b.caret
- %ul.dropdown-menu
- %li
- = link_to explore_projects_filter_path(tag: nil) do
- Any
- - @tags.each do |tag|
- %li{ class: (tag.name == params[:tag]) ? 'active' : 'light' }
- = link_to explore_projects_filter_path(tag: tag.name) do
- %i.fa.fa-tag
- = tag.name
+ - @tags.each do |tag|
+ %li{ class: (tag.name == params[:tag]) ? 'active' : 'light' }
+ = link_to explore_projects_filter_path(tag: tag.name) do
+ %i.fa.fa-tag
+ = tag.name
diff --git a/app/views/explore/projects/_projects.html.haml b/app/views/explore/projects/_projects.html.haml
index 999a933390b..708fbc27f55 100644
--- a/app/views/explore/projects/_projects.html.haml
+++ b/app/views/explore/projects/_projects.html.haml
@@ -1,6 +1 @@
-- if projects.any?
- .projects-list-holder
- = render 'shared/projects/list', projects: projects
-- else
- .nothing-here-block
- No such projects
+= render 'shared/projects/list', projects: projects
diff --git a/app/views/explore/projects/index.html.haml b/app/views/explore/projects/index.html.haml
index dca75498573..42b50481b9d 100644
--- a/app/views/explore/projects/index.html.haml
+++ b/app/views/explore/projects/index.html.haml
@@ -9,7 +9,7 @@
.top-area
= render 'explore/projects/nav'
-.gray-content-block.second-block.clearfix
- = render 'filter'
+ .nav-controls
+ = render 'filter'
= render 'projects', projects: @projects
diff --git a/app/views/groups/_projects.html.haml b/app/views/groups/_projects.html.haml
index 9c16ab7e30f..794aa57b55a 100644
--- a/app/views/groups/_projects.html.haml
+++ b/app/views/groups/_projects.html.haml
@@ -1,11 +1,11 @@
.top-area
.nav-controls
= form_tag request.original_url, method: :get, class: 'project-filter-form', id: 'project-filter-form' do |f|
- = search_field_tag :filter_projects, params[:filter_projects], placeholder: 'Filter by name...', class: 'input-short project-filter-form-field form-control projects-list-filter', spellcheck: false, id: 'project-filter-form-field'
- - if current_user && current_user.can_create_project?
- = link_to new_project_path, class: 'btn btn-new' do
- = icon('plus')
- New Project
+ - if @projects.present?
+ = search_field_tag :filter_projects, nil, placeholder: 'Filter by name', class: 'projects-list-filter form-control', spellcheck: false
+ - if can? current_user, :create_projects, @group
+ = link_to new_project_path(namespace_id: @group.id), class: 'btn btn-new pull-right' do
+ = icon('plus')
+ New Project
-.projects-list-holder
- = render 'shared/projects/list', projects: @projects, projects_limit: 20, stars: false, skip_namespace: true
+= render 'shared/projects/list', projects: @projects, stars: false, skip_namespace: true
diff --git a/app/views/groups/group_members/_group_member.html.haml b/app/views/groups/group_members/_group_member.html.haml
index a79a0fcdc8e..60234be8f83 100644
--- a/app/views/groups/group_members/_group_member.html.haml
+++ b/app/views/groups/group_members/_group_member.html.haml
@@ -1,5 +1,6 @@
- user = member.user
- return unless user || member.invite?
+- show_roles = local_assigns.fetch(:show_roles, true)
%li{class: "#{dom_class(member)} js-toggle-container", id: dom_id(member)}
%span{class: ("list-item-name" if show_controls)}
@@ -28,7 +29,7 @@
= link_to resend_invite_group_group_member_path(@group, member), method: :post, class: "btn-xs btn", title: 'Resend invite' do
Resend invite
- - if should_user_see_group_roles?(current_user, @group)
+ - if show_roles && should_user_see_group_roles?(current_user, @group)
%span.pull-right
%strong.member-access-level= member.human_access
- if show_controls
diff --git a/app/views/groups/milestones/_issue.html.haml b/app/views/groups/milestones/_issue.html.haml
deleted file mode 100644
index 9b85d83d6d8..00000000000
--- a/app/views/groups/milestones/_issue.html.haml
+++ /dev/null
@@ -1,10 +0,0 @@
-%li{ id: dom_id(issue, 'sortable'), class: 'issue-row', 'data-iid' => issue.iid }
- %span.milestone-row
- - project = issue.project
- %strong #{project.name} &middot;
- = link_to [project.namespace.becomes(Namespace), project, issue] do
- %span.cgray ##{issue.iid}
- = link_to_gfm issue.title, [project.namespace.becomes(Namespace), project, issue], title: issue.title
- .pull-right.assignee-icon
- - if issue.assignee
- = image_tag avatar_icon(issue.assignee, 16), class: "avatar s16", alt: ''
diff --git a/app/views/groups/milestones/_issues.html.haml b/app/views/groups/milestones/_issues.html.haml
deleted file mode 100644
index 9f350b772bd..00000000000
--- a/app/views/groups/milestones/_issues.html.haml
+++ /dev/null
@@ -1,6 +0,0 @@
-.panel.panel-default
- .panel-heading= title
- %ul{ class: "well-list issues-sortable-list" }
- - if issues
- - issues.each do |issue|
- = render 'issue', issue: issue
diff --git a/app/views/groups/milestones/_merge_request.html.haml b/app/views/groups/milestones/_merge_request.html.haml
deleted file mode 100644
index e3aa4aad198..00000000000
--- a/app/views/groups/milestones/_merge_request.html.haml
+++ /dev/null
@@ -1,10 +0,0 @@
-%li{ id: dom_id(merge_request, 'sortable'), class: 'mr-row', 'data-iid' => merge_request.iid }
- %span.milestone-row
- - project = merge_request.project
- %strong #{project.name} &middot;
- = link_to [project.namespace.becomes(Namespace), project, merge_request] do
- %span.cgray ##{merge_request.iid}
- = link_to_gfm merge_request.title, [project.namespace.becomes(Namespace), project, merge_request], title: merge_request.title
- .pull-right.assignee-icon
- - if merge_request.assignee
- = image_tag avatar_icon(merge_request.assignee, 16), class: "avatar s16", alt: ''
diff --git a/app/views/groups/milestones/_merge_requests.html.haml b/app/views/groups/milestones/_merge_requests.html.haml
deleted file mode 100644
index 50057e2c636..00000000000
--- a/app/views/groups/milestones/_merge_requests.html.haml
+++ /dev/null
@@ -1,6 +0,0 @@
-.panel.panel-default
- .panel-heading= title
- %ul{ class: "well-list merge_requests-sortable-list" }
- - if merge_requests
- - merge_requests.each do |merge_request|
- = render 'merge_request', merge_request: merge_request
diff --git a/app/views/groups/milestones/_milestone.html.haml b/app/views/groups/milestones/_milestone.html.haml
index a20bf75bc39..4c4e0a26728 100644
--- a/app/views/groups/milestones/_milestone.html.haml
+++ b/app/views/groups/milestones/_milestone.html.haml
@@ -1,29 +1,5 @@
-%li{class: "milestone milestone-#{milestone.closed? ? 'closed' : 'open'}", id: dom_id(milestone.milestones.first) }
- .row
- .col-sm-6
- %strong
- = link_to_gfm truncate(milestone.title, length: 100), group_milestone_path(@group, milestone.safe_title, title: milestone.title)
- .col-sm-6
- .pull-right.light #{milestone.percent_complete}% complete
- .row
- .col-sm-6
- = link_to issues_group_path(@group, milestone_title: milestone.title) do
- = pluralize milestone.issue_count, 'Issue'
- &middot;
- = link_to merge_requests_group_path(@group, milestone_title: milestone.title) do
- = pluralize milestone.merge_requests_count, 'Merge Request'
- .col-sm-6
- = milestone_progress_bar(milestone)
- .row
- .col-sm-6
- %div
- - milestone.milestones.each do |milestone|
- = link_to milestone_path(milestone) do
- %span.label.label-gray
- = milestone.project.name
- .col-sm-6
- - if can?(current_user, :admin_milestones, @group)
- - if milestone.closed?
- = link_to 'Reopen Milestone', group_milestone_path(@group, milestone.safe_title, title: milestone.title, milestone: {state_event: :activate }), method: :put, class: "btn btn-xs btn-grouped btn-reopen"
- - else
- = link_to 'Close Milestone', group_milestone_path(@group, milestone.safe_title, title: milestone.title, milestone: {state_event: :close }), method: :put, class: "btn btn-xs btn-close"
+= render 'shared/milestones/milestone',
+ milestone_path: group_milestone_path(@group, milestone.safe_title, title: milestone.title),
+ issues_path: issues_group_path(@group, milestone_title: milestone.title),
+ merge_requests_path: merge_requests_group_path(@group, milestone_title: milestone.title),
+ milestone: milestone
diff --git a/app/views/groups/milestones/new.html.haml b/app/views/groups/milestones/new.html.haml
index 3894a0ece74..a8e1ed77da9 100644
--- a/app/views/groups/milestones/new.html.haml
+++ b/app/views/groups/milestones/new.html.haml
@@ -8,18 +8,18 @@
This will create milestone in every selected project
%hr
-= form_for @milestone, url: group_milestones_path(@group), html: { class: 'form-horizontal milestone-form gfm-form js-requires-input' } do |f|
+= form_for @milestone, url: group_milestones_path(@group), html: { class: 'form-horizontal milestone-form gfm-form js-quick-submit js-requires-input' } do |f|
.row
.col-md-6
.form-group
= f.label :title, "Title", class: "control-label"
.col-sm-10
- = f.text_field :title, maxlength: 255, class: "form-control js-quick-submit", required: true, autofocus: true
+ = f.text_field :title, maxlength: 255, class: "form-control", required: true, autofocus: true
.form-group.milestone-description
= f.label :description, "Description", class: "control-label"
.col-sm-10
= render layout: 'projects/md_preview', locals: { preview_class: "md-preview" } do
- = render 'projects/zen', f: f, attr: :description, classes: 'description form-control js-quick-submit'
+ = render 'projects/zen', f: f, attr: :description, classes: 'description form-control'
.clearfix
.error-alert
.form-group
diff --git a/app/views/groups/milestones/show.html.haml b/app/views/groups/milestones/show.html.haml
index 1233da85524..fb6f0da28f8 100644
--- a/app/views/groups/milestones/show.html.haml
+++ b/app/views/groups/milestones/show.html.haml
@@ -1,112 +1,4 @@
-- page_title @milestone.title, "Milestones"
= render "header_title"
-
-.detail-page-header
- .status-box{ class: "status-box-#{@milestone.closed? ? 'closed' : 'open'}" }
- - if @milestone.closed?
- Closed
- - else
- Open
- %span.identifier
- Milestone #{@milestone.title}
- .pull-right
- - if can?(current_user, :admin_milestones, @group)
- - if @milestone.active?
- = link_to 'Close Milestone', group_milestone_path(@group, @milestone.safe_title, title: @milestone.title, milestone: {state_event: :close }), method: :put, class: "btn btn-grouped btn-close"
- - else
- = link_to 'Reopen Milestone', group_milestone_path(@group, @milestone.safe_title, title: @milestone.title, milestone: {state_event: :activate }), method: :put, class: "btn btn-grouped btn-reopen"
-
-.detail-page-description.gray-content-block.second-block
- %h2.title
- = markdown escape_once(@milestone.title), pipeline: :single_line
-
-- if @milestone.complete? && @milestone.active?
- .alert.alert-success.prepend-top-default
- %span All issues for this milestone are closed. You may close the milestone now.
-
-.table-holder
- %table.table
- %thead
- %tr
- %th Project
- %th Open issues
- %th State
- %th Due date
- - @milestone.milestones.each do |milestone|
- %tr
- %td
- = link_to "#{milestone.project.name}", namespace_project_milestone_path(milestone.project.namespace, milestone.project, milestone)
- %td
- = milestone.issues.opened.count
- %td
- - if milestone.closed?
- Closed
- - else
- Open
- %td
- = milestone.expires_at
-
-.context
- %p.lead
- Progress:
- #{@milestone.closed_items_count} closed
- &ndash;
- #{@milestone.open_items_count} open
- = milestone_progress_bar(@milestone)
-
-%ul.nav-links.no-top.no-bottom
- %li.active
- = link_to '#tab-issues', 'data-toggle' => 'tab' do
- Issues
- %span.badge= @milestone.issue_count
- %li
- = link_to '#tab-merge-requests', 'data-toggle' => 'tab' do
- Merge Requests
- %span.badge= @milestone.merge_requests_count
- %li
- = link_to '#tab-participants', 'data-toggle' => 'tab' do
- Participants
- %span.badge= @milestone.participants.count
-
-.tab-content
- .tab-pane.active#tab-issues
- .gray-content-block.middle-block
- .pull-right
- = link_to 'Browse Issues', issues_group_path(@group, milestone_title: @milestone.title), class: "btn btn-grouped"
-
- .oneline
- All issues in this milestone
-
- .row.prepend-top-default
- .col-md-6
- = render 'issues', title: "Open", issues: @milestone.opened_issues
- .col-md-6
- = render 'issues', title: "Closed", issues: @milestone.closed_issues
-
- .tab-pane#tab-merge-requests
- .gray-content-block.middle-block
- .pull-right
- = link_to 'Browse Merge Requests', merge_requests_group_path(@group, milestone_title: @milestone.title), class: "btn btn-grouped"
-
- .oneline
- All merge requests in this milestone
-
- .row.prepend-top-default
- .col-md-6
- = render 'merge_requests', title: "Open", merge_requests: @milestone.opened_merge_requests
- .col-md-6
- = render 'merge_requests', title: "Closed", merge_requests: @milestone.closed_merge_requests
-
- .tab-pane#tab-participants
- .gray-content-block.middle-block
- .oneline
- All participants to this milestone
-
- %ul.bordered-list
- - @milestone.participants.each do |user|
- %li
- = link_to user, title: user.name, class: "darken" do
- = image_tag avatar_icon(user, 32), class: "avatar s32"
- %strong= truncate(user.name, lenght: 40)
- %br
- %small.cgray= user.username
+= render 'shared/milestones/top', milestone: @milestone, group: @group
+= render 'shared/milestones/summary', milestone: @milestone
+= render 'shared/milestones/tabs', milestone: @milestone, show_project_name: true
diff --git a/app/views/groups/show.html.haml b/app/views/groups/show.html.haml
index a0ba11b11a1..6148d8cb3d2 100644
--- a/app/views/groups/show.html.haml
+++ b/app/views/groups/show.html.haml
@@ -32,10 +32,9 @@
%li.active
= link_to "#activity", 'data-toggle' => 'tab' do
Activity
- - if @projects.present?
- %li
- = link_to "#projects", 'data-toggle' => 'tab' do
- Projects
+ %li
+ = link_to "#projects", 'data-toggle' => 'tab' do
+ Projects
- if can?(current_user, :read_group, @group)
%div{ class: container_class }
diff --git a/app/views/help/_shortcuts.html.haml b/app/views/help/_shortcuts.html.haml
index 8e982718d23..82d2d4aabed 100644
--- a/app/views/help/_shortcuts.html.haml
+++ b/app/views/help/_shortcuts.html.haml
@@ -229,6 +229,10 @@
%td.shortcut
.key r
%td Reply (quoting selected text)
+ %tr
+ %td.shortcut
+ .key e
+ %td Edit issue
%tbody{ class: 'hidden-shortcut merge_requests', style: 'display:none' }
%tr
%th
@@ -245,3 +249,7 @@
%td.shortcut
.key r
%td Reply (quoting selected text)
+ %tr
+ %td.shortcut
+ .key e
+ %td Edit merge request
diff --git a/app/views/help/ui.html.haml b/app/views/help/ui.html.haml
index 746386cab58..a2c0a858930 100644
--- a/app/views/help/ui.html.haml
+++ b/app/views/help/ui.html.haml
@@ -31,64 +31,91 @@
%h2#blocks Blocks
- %h4
+ .lead
+ Content block separated with botton border
+ %code .content-block
+
+ .example
+ .content-block
+ %h4 Normal block inside content
+ = lorem
+
+ .content-block
+ %h4 Second block
+ = lorem
+
+ .lead
+ Gray content block with side padding using
%code .gray-content-block
- .gray-content-block.middle-block
- %h4 Normal block inside content
- = lorem
+ .example
+ .gray-content-block
+ %h4 Normal block inside content
+ = lorem
- .gray-content-block.second-block
- %h4 Second block
- = lorem
+ .gray-content-block.second-block
+ %h4 Second block
+ = lorem
- %h4
+ .lead
+ Cover block for profile page with avatar, name and description
%code .cover-block
- %br
- .cover-block
- .avatar-holder
- = image_tag avatar_icon('admin@example.com', 90), class: "avatar s90", alt: ''
- .cover-title
- John Smith
-
- .cover-desc
- = lorem
+ .example
+ .cover-block
+ .avatar-holder
+ = image_tag avatar_icon('admin@example.com', 90), class: "avatar s90", alt: ''
+ .cover-title
+ John Smith
+
+ .cover-desc
+ = lorem
- .cover-controls
- = link_to '#', class: 'btn btn-gray' do
- = icon('pencil')
- &nbsp;
- = link_to '#', class: 'btn btn-gray' do
- = icon('rss')
+ .cover-controls
+ = link_to '#', class: 'btn btn-gray' do
+ = icon('pencil')
+ &nbsp;
+ = link_to '#', class: 'btn btn-gray' do
+ = icon('rss')
%h2#lists Lists
- %h4
+ .lead
+ Simple list using
%code .content-list
- %ul.content-list
- %li
- One item
- %li
- One item
- %li
- One item
- %h4
- %code .well-list
- %ul.well-list
- %li
- One item
- %li
- One item
- %li
- One item
+ .example
+ %ul.content-list
+ %li
+ One item
+ %li
+ One item
+ %li
+ One item
- %h4
- %code .panel .well-list
+ .lead
+ List with avatar, title and description using
+ %code .content-list
+
+ .example
+ %ul.content-list
+ %li
+ = image_tag 'no_avatar.png', class: 'avatar s40'
+ .title Title
+ .description Description
+ %li
+ = image_tag 'no_avatar.png', class: 'avatar s40'
+ .title Title
+ .description Description
+ %li
+ = image_tag 'no_avatar.png', class: 'avatar s40'
+ .title Title
+ .description Description
- .panel.panel-default
- .panel-heading Your list
+ .lead
+ List with hover effect
+ %code .well-list
+ .example
%ul.well-list
%li
One item
@@ -97,17 +124,18 @@
%li
One item
- %h4
- %code .bordered-list
- %ul.bordered-list
- %li
- One item
- %li
- One item
- %li
- One item
-
-
+ .lead
+ List inside panel
+ .example
+ .panel.panel-default
+ .panel-heading Your list
+ %ul.well-list
+ %li
+ One item
+ %li
+ One item
+ %li
+ One item
%h2#tables Tables
@@ -138,9 +166,9 @@
%h2#navs Navigation
- %h4
+ .lead
+ Holder for top page navigation. Includes navigation, search field, sorting and button
%code .top-area
- %p Holder for top page navigation. Includes navigation, search field, sorting and button
.example
.top-area
@@ -161,9 +189,9 @@
= link_to 'New issue', '#', class: 'btn btn-new'
- %h4
+ .lead
+ Only nav links without button and search
%code .nav-links
- %p Only nav links without button and search
.example
%ul.nav-links
%li.active
@@ -228,43 +256,47 @@
%h2#forms Forms
- %h4
+ .lead
+ Horizontal form when label rendered inline with input
%code form.horizontal-form
- %form.form-horizontal
- .form-group
- %label.col-sm-2.control-label{:for => "inputEmail3"} Email
- .col-sm-10
- %input#inputEmail3.form-control{:placeholder => "Email", :type => "email"}/
- .form-group
- %label.col-sm-2.control-label{:for => "inputPassword3"} Password
- .col-sm-10
- %input#inputPassword3.form-control{:placeholder => "Password", :type => "password"}/
- .form-group
- .col-sm-offset-2.col-sm-10
- .checkbox
- %label
- %input{:type => "checkbox"}/
- Remember me
- .form-group
- .col-sm-offset-2.col-sm-10
- %button.btn.btn-default{:type => "submit"} Sign in
-
- %h4
+ .example
+ %form.form-horizontal
+ .form-group
+ %label.col-sm-2.control-label{:for => "inputEmail3"} Email
+ .col-sm-10
+ %input#inputEmail3.form-control{:placeholder => "Email", :type => "email"}/
+ .form-group
+ %label.col-sm-2.control-label{:for => "inputPassword3"} Password
+ .col-sm-10
+ %input#inputPassword3.form-control{:placeholder => "Password", :type => "password"}/
+ .form-group
+ .col-sm-offset-2.col-sm-10
+ .checkbox
+ %label
+ %input{:type => "checkbox"}/
+ Remember me
+ .form-group
+ .col-sm-offset-2.col-sm-10
+ %button.btn.btn-default{:type => "submit"} Sign in
+
+ .lead
+ Form when label rendered above input
%code form
- %form
- .form-group
- %label{:for => "exampleInputEmail1"} Email address
- %input#exampleInputEmail1.form-control{:placeholder => "Enter email", :type => "email"}/
- .form-group
- %label{:for => "exampleInputPassword1"} Password
- %input#exampleInputPassword1.form-control{:placeholder => "Password", :type => "password"}/
- .checkbox
- %label
- %input{:type => "checkbox"}/
- Remember me
- %button.btn.btn-default{:type => "submit"} Sign in
+ .example
+ %form
+ .form-group
+ %label{:for => "exampleInputEmail1"} Email address
+ %input#exampleInputEmail1.form-control{:placeholder => "Enter email", :type => "email"}/
+ .form-group
+ %label{:for => "exampleInputPassword1"} Password
+ %input#exampleInputPassword1.form-control{:placeholder => "Password", :type => "password"}/
+ .checkbox
+ %label
+ %input{:type => "checkbox"}/
+ Remember me
+ %button.btn.btn-default{:type => "submit"} Sign in
%h2#file File
%h4
diff --git a/app/views/layouts/_head.html.haml b/app/views/layouts/_head.html.haml
index 38ca4f91c4d..79cdbac1f37 100644
--- a/app/views/layouts/_head.html.haml
+++ b/app/views/layouts/_head.html.haml
@@ -44,6 +44,7 @@
= favicon_link_tag 'touch-icon-ipad.png', rel: 'apple-touch-icon', sizes: '76x76'
= favicon_link_tag 'touch-icon-iphone-retina.png', rel: 'apple-touch-icon', sizes: '120x120'
= favicon_link_tag 'touch-icon-ipad-retina.png', rel: 'apple-touch-icon', sizes: '152x152'
+ %link{rel: 'mask-icon', href: image_path('logo.svg'), color: 'rgb(226, 67, 41)'}
-# Windows 8 pinned site tile
%meta{name: 'msapplication-TileImage', content: image_path('msapplication-tile.png')}
diff --git a/app/views/layouts/_page.html.haml b/app/views/layouts/_page.html.haml
index e53d5b07801..c799e9c588d 100644
--- a/app/views/layouts/_page.html.haml
+++ b/app/views/layouts/_page.html.haml
@@ -4,7 +4,7 @@
.header-logo
%a#logo
= brand_header_logo
- = link_to root_path, class: 'home', title: 'Dashboard', id: 'js-shortcuts-home' do
+ = link_to root_path, class: 'gitlab-text-container-link', title: 'Dashboard', id: 'js-shortcuts-home' do
.gitlab-text-container
%h3 GitLab
diff --git a/app/views/layouts/_search.html.haml b/app/views/layouts/_search.html.haml
index 20042e21bf2..54af2c3063c 100644
--- a/app/views/layouts/_search.html.haml
+++ b/app/views/layouts/_search.html.haml
@@ -1,6 +1,6 @@
.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
+ = search_field_tag "search", nil, placeholder: 'Search', class: "search-input form-control", spellcheck: false, tabindex: "1"
= hidden_field_tag :group_id, @group.try(:id)
- if @project && @project.persisted?
= hidden_field_tag :project_id, @project.id
diff --git a/app/views/layouts/application.html.haml b/app/views/layouts/application.html.haml
index 678ed3c2c1f..babfb032236 100644
--- a/app/views/layouts/application.html.haml
+++ b/app/views/layouts/application.html.haml
@@ -5,11 +5,7 @@
-# Ideally this would be inside the head, but turbolinks only evaluates page-specific JS in the body.
= yield :scripts_body_top
- - if current_user
- = render "layouts/header/default", title: header_title
- - else
- = render "layouts/header/public", title: header_title
-
+ = render "layouts/header/default", title: header_title
= render 'layouts/page', sidebar: sidebar
= yield :scripts_body
diff --git a/app/views/layouts/ci/_page.html.haml b/app/views/layouts/ci/_page.html.haml
index 3cfd36720f0..a13241bebee 100644
--- a/app/views/layouts/ci/_page.html.haml
+++ b/app/views/layouts/ci/_page.html.haml
@@ -4,7 +4,7 @@
.header-logo
%a#logo
= brand_header_logo
- = link_to root_path, class: 'home', title: 'Dashboard', id: 'js-shortcuts-home' do
+ = link_to root_path, class: 'gitlab-text-container-link', title: 'Dashboard', id: 'js-shortcuts-home' do
.gitlab-text-container
%h3 GitLab
diff --git a/app/views/layouts/header/_default.html.haml b/app/views/layouts/header/_default.html.haml
index fcb6b835a7e..77d01a7736c 100644
--- a/app/views/layouts/header/_default.html.haml
+++ b/app/views/layouts/header/_default.html.haml
@@ -13,30 +13,39 @@
%li.visible-sm.visible-xs
= link_to search_path, title: 'Search', data: {toggle: 'tooltip', placement: 'bottom', container: 'body'} do
= icon('search')
- - if session[:impersonator_id]
- %li.impersonation
- = link_to stop_impersonation_admin_users_path, method: :delete, title: 'Stop Impersonation', data: { toggle: 'tooltip', placement: 'bottom', container: 'body' } do
- = icon('user-secret fw')
- - if current_user.is_admin?
+ - if current_user
+ - if session[:impersonator_id]
+ %li.impersonation
+ = link_to stop_impersonation_admin_users_path, method: :delete, title: 'Stop Impersonation', data: { toggle: 'tooltip', placement: 'bottom', container: 'body' } do
+ = icon('user-secret fw')
+ - if current_user.is_admin?
+ %li
+ = link_to admin_root_path, title: 'Admin Area', data: {toggle: 'tooltip', placement: 'bottom', container: 'body'} do
+ = icon('wrench fw')
%li
- = link_to admin_root_path, title: 'Admin Area', data: {toggle: 'tooltip', placement: 'bottom', container: 'body'} do
- = icon('wrench fw')
- - if current_user.can_create_project?
+ = link_to dashboard_todos_path, title: 'Todos', data: {toggle: 'tooltip', placement: 'bottom', container: 'body'} do
+ %span.badge.todos-pending-count
+ = todos_pending_count
+ - if current_user.can_create_project?
+ %li
+ = link_to new_project_path, title: 'New project', data: {toggle: 'tooltip', placement: 'bottom', container: 'body'} do
+ = icon('plus fw')
+ - if Gitlab::Sherlock.enabled?
+ %li
+ = link_to sherlock_transactions_path, title: 'Sherlock Transactions',
+ data: {toggle: 'tooltip', placement: 'bottom', container: 'body'} do
+ = icon('tachometer fw')
%li
- = link_to new_project_path, title: 'New project', data: {toggle: 'tooltip', placement: 'bottom', container: 'body'} do
- = icon('plus fw')
- - if Gitlab::Sherlock.enabled?
- %li
- = link_to sherlock_transactions_path, title: 'Sherlock Transactions',
- data: {toggle: 'tooltip', placement: 'bottom', container: 'body'} do
- = icon('tachometer fw')
- %li
- = link_to destroy_user_session_path, class: 'logout', method: :delete, title: 'Sign out', data: {toggle: 'tooltip', placement: 'bottom', container: 'body'} do
- = icon('sign-out')
+ = link_to destroy_user_session_path, class: 'logout', method: :delete, title: 'Sign out', data: {toggle: 'tooltip', placement: 'bottom', container: 'body'} do
+ = icon('sign-out')
+ - else
+ .pull-right
+ = link_to "Sign in", new_session_path(:user, redirect_to_referer: 'yes'), class: 'btn btn-sign-in btn-success'
+
%h1.title= title
= render 'shared/outdated_browser'
- if @project && !@project.empty_repo?
:javascript
- var findFileURL = "#{namespace_project_find_file_path(@project.namespace, @project, @ref || @project.repository.root_ref)}"; \ No newline at end of file
+ var findFileURL = "#{namespace_project_find_file_path(@project.namespace, @project, @ref || @project.repository.root_ref)}";
diff --git a/app/views/layouts/header/_public.html.haml b/app/views/layouts/header/_public.html.haml
deleted file mode 100644
index a6a26518a0e..00000000000
--- a/app/views/layouts/header/_public.html.haml
+++ /dev/null
@@ -1,10 +0,0 @@
-%header.navbar.navbar-fixed-top.navbar-gitlab{ class: nav_header_class }
- %div{ class: fluid_layout ? "container-fluid" : "container-fluid" }
- .header-content
- - unless current_controller?('sessions')
- .pull-right
- = link_to "Sign in", new_session_path(:user, redirect_to_referer: 'yes'), class: 'btn btn-sign-in btn-success'
-
- %h1.title= title
-
-= render 'shared/outdated_browser'
diff --git a/app/views/layouts/nav/_admin.html.haml b/app/views/layouts/nav/_admin.html.haml
index ac1d5429382..280a1b93729 100644
--- a/app/views/layouts/nav/_admin.html.haml
+++ b/app/views/layouts/nav/_admin.html.haml
@@ -56,6 +56,11 @@
= icon('cog fw')
%span
Background Jobs
+ = nav_link(controller: :appearances) do
+ = link_to admin_appearances_path, title: 'Appearances' do
+ = icon('image')
+ %span
+ Appearance
= nav_link(controller: :applications) do
= link_to admin_applications_path, title: 'Applications' do
diff --git a/app/views/layouts/nav/_dashboard.html.haml b/app/views/layouts/nav/_dashboard.html.haml
index 106abd24a56..db0cf393922 100644
--- a/app/views/layouts/nav/_dashboard.html.haml
+++ b/app/views/layouts/nav/_dashboard.html.haml
@@ -4,6 +4,12 @@
= icon('home fw')
%span
Projects
+ = nav_link(controller: :todos) do
+ = link_to dashboard_todos_path, title: 'Todos' do
+ = icon('bell fw')
+ %span
+ Todos
+ %span.count= number_with_delimiter(todos_pending_count)
= nav_link(path: 'dashboard#activity') do
= link_to activity_dashboard_path, class: 'shortcuts-activity', title: 'Activity' do
= icon('dashboard fw')
@@ -25,12 +31,12 @@
%span
Issues
%span.count= number_with_delimiter(current_user.assigned_issues.opened.count)
- = nav_link(path: 'dashboard#merge_requests') do
- = link_to assigned_mrs_dashboard_path, title: 'Merge Requests', class: 'shortcuts-merge_requests' do
- = icon('tasks fw')
- %span
- Merge Requests
- %span.count= number_with_delimiter(current_user.assigned_merge_requests.opened.count)
+ = nav_link(path: 'dashboard#merge_requests') do
+ = link_to assigned_mrs_dashboard_path, title: 'Merge Requests', class: 'shortcuts-merge_requests' do
+ = icon('tasks fw')
+ %span
+ Merge Requests
+ %span.count= number_with_delimiter(current_user.assigned_merge_requests.opened.count)
= nav_link(controller: :snippets) do
= link_to dashboard_snippets_path, title: 'Snippets' do
= icon('clipboard fw')
diff --git a/app/views/profiles/_event_table.html.haml b/app/views/profiles/_event_table.html.haml
index 58af79716a7..879fc170f92 100644
--- a/app/views/profiles/_event_table.html.haml
+++ b/app/views/profiles/_event_table.html.haml
@@ -1,17 +1,15 @@
-.table-holder
- %table.table#audits
- %thead
- %tr
- %th Action
- %th When
+%h5.prepend-top-0
+ History of authentications
+
+%ul.well-list
+ - events.each do |event|
+ %li
+ %span.description
+ = audit_icon(event.details[:with], class: "append-right-5")
+ Signed in with
+ = event.details[:with]
+ authentication
+ %span.pull-right
+ #{time_ago_in_words event.created_at} ago
- %tbody
- - events.each do |event|
- %tr
- %td
- %span
- Signed in with
- %b= event.details[:with]
- authentication
- %td #{time_ago_in_words event.created_at} ago
= paginate events, theme: "gitlab"
diff --git a/app/views/profiles/applications.html.haml b/app/views/profiles/applications.html.haml
index 0436c2213da..86f35823406 100644
--- a/app/views/profiles/applications.html.haml
+++ b/app/views/profiles/applications.html.haml
@@ -1,7 +1,7 @@
- page_title "Applications"
- header_title page_title, applications_profile_path
-.gray-content-block.top-block
+.alert.alert-help.prepend-top-default
- if user_oauth_applications?
Manage applications that can use GitLab as an OAuth provider,
and applications that you've authorized to use your account.
diff --git a/app/views/profiles/audit_log.html.haml b/app/views/profiles/audit_log.html.haml
index 8fdba45b193..f630c03e5f6 100644
--- a/app/views/profiles/audit_log.html.haml
+++ b/app/views/profiles/audit_log.html.haml
@@ -1,8 +1,11 @@
- page_title "Audit Log"
- header_title page_title, audit_log_profile_path
-.gray-content-block.top-block
- History of authentications
-
-.prepend-top-default
-= render 'event_table', events: @events
+.row.prepend-top-default
+ .col-lg-3.profile-settings-sidebar
+ %h3.prepend-top-0
+ = page_title
+ %p
+ This is a security log of important events involving your account.
+ .col-lg-9
+ = render 'event_table', events: @events
diff --git a/app/views/profiles/emails/index.html.haml b/app/views/profiles/emails/index.html.haml
index 1d140347a5f..3f328f96cea 100644
--- a/app/views/profiles/emails/index.html.haml
+++ b/app/views/profiles/emails/index.html.haml
@@ -1,52 +1,49 @@
- page_title "Emails"
- header_title page_title, profile_emails_path
-.gray-content-block.top-block
- Control emails linked to your account
-
-%ul.prepend-top-default
- %li
- Your
- %b Primary Email
- will be used for avatar detection and web based operations, such as edits and merges.
- %li
- Your
- %b Notification Email
- will be used for account notifications.
- %li
- Your
- %b Public Email
- will be displayed on your public profile.
- %li
- All email addresses will be used to identify your commits.
-
-.panel.panel-default
- .panel-heading
- Emails (#{@emails.count + 1})
- %ul.well-list#emails-table
- %li
- %strong= @primary
- %span.label.label-success Primary Email
- - if @primary === current_user.public_email
- %span.label.label-info Public Email
- - if @primary === current_user.notification_email
- %span.label.label-info Notification Email
- - @emails.each do |email|
+.row.prepend-top-default
+ .col-lg-3.profile-settings-sidebar
+ %h4.prepend-top-0
+ = page_title
+ %p
+ Control emails linked to your account
+ .col-lg-9
+ %h4.prepend-top-0
+ Add email address
+ = form_for 'email', url: profile_emails_path do |f|
+ .form-group
+ = f.label :email, class: 'label-light'
+ = f.text_field :email, class: 'form-control'
+ .prepend-top-default
+ = f.submit 'Add email address', class: 'btn btn-create'
+ %hr
+ %h4.prepend-top-0
+ Linked emails (#{@emails.count + 1})
+ .account-well.append-bottom-default
+ %ul
+ %li
+ Your Primary Email will be used for avatar detection and web based operations, such as edits and merges.
+ %li
+ Your Notification Email will be used for account notifications.
+ %li
+ Your Public Email will be displayed on your public profile.
+ %li
+ All email addresses will be used to identify your commits.
+ %ul.well-list
%li
- %strong= email.email
- - if email.email === current_user.public_email
- %span.label.label-info Public Email
- - if email.email === current_user.notification_email
- %span.label.label-info Notification Email
- %span.cgray
- added #{time_ago_with_tooltip(email.created_at)}
- = link_to 'Remove', profile_email_path(email), data: { confirm: 'Are you sure?'}, method: :delete, class: 'btn btn-sm btn-remove pull-right'
-
-%h4 Add email address
-= form_for 'email', url: profile_emails_path, html: { class: 'form-horizontal' } do |f|
- .form-group
- = f.label :email, class: 'control-label'
- .col-sm-10
- = f.text_field :email, class: 'form-control'
- .form-actions
- = f.submit 'Add email address', class: 'btn btn-create'
+ = @primary
+ %span.pull-right
+ %span.label.label-success Primary Email
+ - if @primary === current_user.public_email
+ %span.label.label-info Public Email
+ - if @primary === current_user.notification_email
+ %span.label.label-info Notification Email
+ - @emails.each do |email|
+ %li
+ = email.email
+ %span.pull-right
+ - if email.email === current_user.public_email
+ %span.label.label-info Public Email
+ - if email.email === current_user.notification_email
+ %span.label.label-info Notification Email
+ = link_to 'Remove', profile_email_path(email), data: { confirm: 'Are you sure?'}, method: :delete, class: 'btn btn-sm btn-remove pull-right'
diff --git a/app/views/profiles/keys/_form.html.haml b/app/views/profiles/keys/_form.html.haml
index 2a8800de60e..4d78215ed3c 100644
--- a/app/views/profiles/keys/_form.html.haml
+++ b/app/views/profiles/keys/_form.html.haml
@@ -1,5 +1,5 @@
%div
- = form_for [:profile, @key], html: { class: 'form-horizontal js-requires-input' } do |f|
+ = form_for [:profile, @key], html: { class: 'js-requires-input' } do |f|
- if @key.errors.any?
.alert.alert-danger
%ul
@@ -7,13 +7,11 @@
%li= msg
.form-group
- = f.label :key, class: 'control-label'
- .col-sm-10
- = f.text_area :key, class: "form-control", rows: 8, autofocus: true, required: true
+ = f.label :key, class: 'label-light'
+ = f.text_area :key, class: "form-control", rows: 8, required: true
.form-group
- = f.label :title, class: 'control-label'
- .col-sm-10= f.text_field :title, class: "form-control", required: true
+ = f.label :title, class: 'label-light'
+ = f.text_field :title, class: "form-control", required: true
- .form-actions
+ .prepend-top-default
= f.submit 'Add key', class: "btn btn-create"
- = link_to "Cancel", profile_keys_path, class: "btn btn-cancel"
diff --git a/app/views/profiles/keys/_key.html.haml b/app/views/profiles/keys/_key.html.haml
index 9bbccbc45ea..25e9e8ff008 100644
--- a/app/views/profiles/keys/_key.html.haml
+++ b/app/views/profiles/keys/_key.html.haml
@@ -1,11 +1,14 @@
-%tr
- %td
- = link_to path_to_key(key, is_admin) do
- %strong= key.title
- %td
- %code.key-fingerprint= key.fingerprint
- %td
- %span.cgray
- added #{time_ago_with_tooltip(key.created_at)}
- %td
- = link_to 'Remove', path_to_key(key, is_admin), data: { confirm: 'Are you sure?'}, method: :delete, class: "btn btn-sm btn-remove delete-key pull-right"
+%li.key-list-item
+ .pull-left.append-right-10
+ = icon 'key', class: "key-icon hidden-xs"
+ .key-list-item-info
+ = link_to path_to_key(key, is_admin), class: "title" do
+ = key.title
+ .description
+ = key.fingerprint
+ .pull-right
+ %span.key-created-at
+ created #{time_ago_with_tooltip(key.created_at)} ago
+ = link_to path_to_key(key, is_admin), data: { confirm: 'Are you sure?'}, method: :delete, class: "btn btn-transparent prepend-left-10" do
+ %span.sr-only Remove
+ = icon('trash')
diff --git a/app/views/profiles/keys/_key_details.html.haml b/app/views/profiles/keys/_key_details.html.haml
index 3bd1f1af162..dd7615400dc 100644
--- a/app/views/profiles/keys/_key_details.html.haml
+++ b/app/views/profiles/keys/_key_details.html.haml
@@ -1,5 +1,5 @@
- is_admin = defined?(admin) ? true : false
-.row
+.row.prepend-top-default
.col-md-4
.panel.panel-default
.panel-heading
diff --git a/app/views/profiles/keys/_key_table.html.haml b/app/views/profiles/keys/_key_table.html.haml
index 8c9d546af4c..296cafa6e31 100644
--- a/app/views/profiles/keys/_key_table.html.haml
+++ b/app/views/profiles/keys/_key_table.html.haml
@@ -1,19 +1,11 @@
-- is_admin = defined?(admin) ? true : false
+- is_admin = local_assigns.fetch(:admin, false)
+
- if @keys.any?
- .table-holder
- %table.table
- %thead.panel-heading
- %tr
- %th Title
- %th Fingerprint
- %th Added at
- %th
- %tbody
- - @keys.each do |key|
- = render 'profiles/keys/key', key: key, is_admin: is_admin
+ %ul.well-list
+ = render partial: 'profiles/keys/key', collection: @keys, locals: { is_admin: is_admin }
- else
- .nothing-here-block
+ %p.profile-settings-message.text-center
- if is_admin
- User has no ssh keys
+ There are no SSH keys associated with this account.
- else
There are no SSH keys with access to your account.
diff --git a/app/views/profiles/keys/index.html.haml b/app/views/profiles/keys/index.html.haml
index 17a4195030e..e0f8c9a5733 100644
--- a/app/views/profiles/keys/index.html.haml
+++ b/app/views/profiles/keys/index.html.haml
@@ -1,14 +1,21 @@
- page_title "SSH Keys"
- header_title page_title, profile_keys_path
-.gray-content-block.top-block
- .pull-right
- = link_to new_profile_key_path, class: "btn btn-new" do
- = icon('plus')
- Add SSH Key
- .oneline
- Before you can add an SSH key you need to
- = link_to "generate it.", help_page_path("ssh", "README")
-
-.prepend-top-default
-= render 'key_table'
+.row.prepend-top-default
+ .col-lg-3.profile-settings-sidebar
+ %h4.prepend-top-0
+ = page_title
+ %p
+ SSH keys allow you to establish a secure connection between your computer and GitLab.
+ .col-lg-9
+ %h5.prepend-top-0
+ Add an SSH key
+ %p.profile-settings-content
+ Before you can add an SSH key you need to
+ = link_to "generate it.", help_page_path("ssh", "README")
+ = render 'form'
+ %hr
+ %h5
+ Your SSH keys (#{@keys.count})
+ %div.append-bottom-default
+ = render 'key_table'
diff --git a/app/views/profiles/keys/new.html.haml b/app/views/profiles/keys/new.html.haml
deleted file mode 100644
index 13a18269d11..00000000000
--- a/app/views/profiles/keys/new.html.haml
+++ /dev/null
@@ -1,17 +0,0 @@
-- page_title "Add SSH Keys"
-%h3.page-title Add an SSH Key
-%p.light
- Paste your public key here. Read more about how to generate a key on #{link_to "the SSH help page", help_page_path("ssh", "README")}.
-%hr
-= render 'form'
-
-:javascript
- $('#key_key').on('focusout', function(){
- var title = $('#key_title'),
- val = $('#key_key').val(),
- comment = val.match(/^\S+ \S+ (.+)\n?$/);
-
- if( comment && comment.length > 1 && title.val() == '' ){
- $('#key_title').val( comment[1] ).change();
- }
- });
diff --git a/app/views/profiles/notifications/_settings.html.haml b/app/views/profiles/notifications/_settings.html.haml
index 742c5c4b68d..d0d044136f6 100644
--- a/app/views/profiles/notifications/_settings.html.haml
+++ b/app/views/profiles/notifications/_settings.html.haml
@@ -1,5 +1,5 @@
-%li
- %span.notification.fa.fa-holder
+%li.notification-list-item
+ %span.notification.fa.fa-holder.append-right-5
- if notification.global?
= notification_icon(@notification)
- else
diff --git a/app/views/profiles/notifications/show.html.haml b/app/views/profiles/notifications/show.html.haml
index 0bcadc965fa..de80abd7f4d 100644
--- a/app/views/profiles/notifications/show.html.haml
+++ b/app/views/profiles/notifications/show.html.haml
@@ -1,11 +1,7 @@
- page_title "Notifications"
- header_title page_title, profile_notifications_path
-.gray-content-block.top-block
- These are your global notification settings.
-
-.prepend-top-default
-= form_for @user, url: profile_notifications_path, method: :put, html: { class: 'update-notifications form-horizontal global-notifications-form' } do |f|
+= form_for @user, url: profile_notifications_path, method: :put, html: { class: 'update-notifications prepend-top-default' } do |f|
-if @user.errors.any?
%div.alert.alert-danger
%ul
@@ -13,65 +9,66 @@
%li= msg
= hidden_field_tag :notification_type, 'global'
+ .row
+ .col-lg-3.profile-settings-sidebar
+ %h4
+ = page_title
+ %p
+ You can specify notification level per group or per project.
+ %p
+ By default, all projects and groups will use the global notifications setting.
+ .col-lg-9
+ %h5
+ Global notification settings
+ .form-group
+ = f.label :notification_email, class: "label-light"
+ = f.select :notification_email, @user.all_emails, { include_blank: false }, class: "select2"
+ .form-group
+ = f.label :notification_level, class: 'label-light'
+ .radio
+ = f.label :notification_level, value: Notification::N_DISABLED do
+ = f.radio_button :notification_level, Notification::N_DISABLED
+ .level-title
+ Disabled
+ %p You will not get any notifications via email
- .form-group
- = f.label :notification_email, class: "control-label"
- .col-sm-10
- = f.select :notification_email, @user.all_emails, { include_blank: false }, class: "form-control"
-
- .form-group
- = f.label :notification_level, class: 'control-label'
- .col-sm-10
- .radio
- = f.label :notification_level, value: Notification::N_DISABLED do
- = f.radio_button :notification_level, Notification::N_DISABLED
- .level-title
- Disabled
- %p You will not get any notifications via email
-
- .radio
- = f.label :notification_level, value: Notification::N_MENTION do
- = f.radio_button :notification_level, Notification::N_MENTION
- .level-title
- On Mention
- %p You will receive notifications only for comments in which you were @mentioned
-
- .radio
- = f.label :notification_level, value: Notification::N_PARTICIPATING do
- = f.radio_button :notification_level, Notification::N_PARTICIPATING
- .level-title
- Participating
- %p You will only receive notifications from related resources (e.g. from your commits or assigned issues)
-
- .radio
- = f.label :notification_level, value: Notification::N_WATCH do
- = f.radio_button :notification_level, Notification::N_WATCH
- .level-title
- Watch
- %p You will receive notifications for any activity
+ .radio
+ = f.label :notification_level, value: Notification::N_MENTION do
+ = f.radio_button :notification_level, Notification::N_MENTION
+ .level-title
+ On Mention
+ %p You will receive notifications only for comments in which you were @mentioned
- .gray-content-block
- = f.submit 'Save changes', class: "btn btn-create"
+ .radio
+ = f.label :notification_level, value: Notification::N_PARTICIPATING do
+ = f.radio_button :notification_level, Notification::N_PARTICIPATING
+ .level-title
+ Participating
+ %p You will only receive notifications from related resources (e.g. from your commits or assigned issues)
-.row.all-notifications.prepend-top-default
- .col-md-6
- %p
- You can also specify notification level per group or per project.
- %br
- By default, all projects and groups will use the notification level set above.
- %h4 Groups:
- %ul.bordered-list
- - @group_members.each do |group_member|
- - notification = Notification.new(group_member)
- = render 'settings', type: 'group', membership: group_member, notification: notification
+ .radio
+ = f.label :notification_level, value: Notification::N_WATCH do
+ = f.radio_button :notification_level, Notification::N_WATCH
+ .level-title
+ Watch
+ %p You will receive notifications for any activity
- .col-md-6
- %p
- To specify the notification level per project of a group you belong to,
- %br
- you need to be a member of the project itself, not only its group.
- %h4 Projects:
- %ul.bordered-list
- - @project_members.each do |project_member|
- - notification = Notification.new(project_member)
- = render 'settings', type: 'project', membership: project_member, notification: notification
+ .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
diff --git a/app/views/profiles/passwords/edit.html.haml b/app/views/profiles/passwords/edit.html.haml
index fab7c45c9b2..afd4f996b62 100644
--- a/app/views/profiles/passwords/edit.html.haml
+++ b/app/views/profiles/passwords/edit.html.haml
@@ -1,20 +1,18 @@
- page_title "Password"
- header_title page_title, edit_profile_password_path
-.gray-content-block.top-block
- - if @user.password_automatically_set?
- Set your password.
- - else
- Change your password or recover your current one.
-
-.update-password.prepend-top-default
- = form_for @user, url: profile_password_path, method: :put, html: { class: 'form-horizontal' } do |f|
- %div
- %p.slead
- - unless @user.password_automatically_set?
- You must provide current password in order to change it.
- %br
- After a successful password update, you will be redirected to the login page where you can log in with your new password.
+.row.prepend-top-default
+ .col-lg-3.profile-settings-sidebar
+ %h4.prepend-top-0
+ = page_title
+ %p
+ After a successful password update, you will be redirected to the login page where you can log in with your new password.
+ .col-lg-9
+ %h5.prepend-top-0
+ Change your password
+ - unless @user.password_automatically_set?
+ or recover your current one
+ = form_for @user, url: profile_password_path, method: :put, html: {class: "update-password"} do |f|
-if @user.errors.any?
.alert.alert-danger
%ul
@@ -22,19 +20,16 @@
%li= msg
- unless @user.password_automatically_set?
.form-group
- = f.label :current_password, class: 'control-label'
- .col-sm-10
- = f.password_field :current_password, required: true, class: 'form-control'
- %div
- = link_to "Forgot your password?", reset_profile_password_path, method: :put
-
- .form-group
- = f.label :password, 'New password', class: 'control-label'
- .col-sm-10
+ = f.label :current_password, class: 'label-light'
+ = f.password_field :current_password, required: true, class: 'form-control'
+ %p.help-block
+ You must provide your current password in order to change it.
+ .form-group
+ = f.label :password, 'New password', class: 'label-light'
= f.password_field :password, required: true, class: 'form-control'
- .form-group
- = f.label :password_confirmation, class: 'control-label'
- .col-sm-10
+ .form-group
+ = f.label :password_confirmation, class: 'label-light'
= f.password_field :password_confirmation, required: true, class: 'form-control'
- .form-actions
- = f.submit 'Save password', class: "btn btn-create"
+ .prepend-top-default.append-bottom-default
+ = f.submit 'Save password', class: "btn btn-create append-right-10"
+ = link_to "I forgot my password", reset_profile_password_path, method: :put, class: "account-btn-link"
diff --git a/app/views/profiles/preferences/show.html.haml b/app/views/profiles/preferences/show.html.haml
index 877589dc390..f80211669fb 100644
--- a/app/views/profiles/preferences/show.html.haml
+++ b/app/views/profiles/preferences/show.html.haml
@@ -1,57 +1,56 @@
- page_title 'Preferences'
- header_title page_title, profile_preferences_path
-- @blank_container = true
-.alert.alert-help
- These settings allow you to customize the appearance and behavior of the site.
- They are saved with your account and will persist to any device you use to
- access the site.
-
-= form_for @user, url: profile_preferences_path, remote: true, method: :put, html: {class: 'js-preferences-form form-horizontal'} do |f|
- .panel.panel-default.application-theme
- .panel-heading
+= form_for @user, url: profile_preferences_path, remote: true, method: :put, html: {class: 'row prepend-top-default js-preferences-form'} do |f|
+ .col-lg-3.profile-settings-sidebar
+ %h4.prepend-top-0
Application theme
- .panel-body
- - Gitlab::Themes.each do |theme|
- = label_tag do
- .preview{class: theme.css_class}
- = f.radio_button :theme_id, theme.id
- = theme.name
-
- .panel.panel-default.syntax-theme
- .panel-heading
+ %p
+ This setting allows you to customize the appearance of the site, ex. sidebar.
+ .col-lg-9.application-theme
+ - Gitlab::Themes.each do |theme|
+ = label_tag do
+ .preview{class: theme.css_class}
+ = f.radio_button :theme_id, theme.id
+ = theme.name
+ .col-sm-12
+ %hr
+ .col-lg-3.profile-settings-sidebar
+ %h4.prepend-top-0
Syntax highlighting theme
- .panel-body
- - Gitlab::ColorSchemes.each do |scheme|
- = label_tag do
- .preview= image_tag "#{scheme.css_class}-scheme-preview.png"
- = f.radio_button :color_scheme_id, scheme.id
- = scheme.name
-
- .panel.panel-default
- .panel-heading
+ %p
+ This setting allow you to customize the appearance of the syntax.
+ .col-lg-9.syntax-theme
+ - Gitlab::ColorSchemes.each do |scheme|
+ = label_tag do
+ .preview= image_tag "#{scheme.css_class}-scheme-preview.png"
+ = f.radio_button :color_scheme_id, scheme.id
+ = scheme.name
+ .col-sm-12
+ %hr
+ .col-lg-3.profile-settings-sidebar
+ %h4.prepend-top-0
Behavior
- .panel-body
- .form-group
- = f.label :layout, class: 'control-label' do
- Layout width
- .col-sm-10
- = f.select :layout, layout_choices, {}, class: 'form-control'
- .help-block
- Choose between fixed (max. 1200px) and fluid (100%) application layout.
- .form-group
- = f.label :dashboard, class: 'control-label' do
- Default Dashboard
- = link_to('(?)', help_page_path('profile', 'preferences') + '#default-dashboard', target: '_blank')
- .col-sm-10
- = f.select :dashboard, dashboard_choices, {}, class: 'form-control'
- .form-group
- = f.label :project_view, class: 'control-label' do
- Project view
- = link_to('(?)', help_page_path('profile', 'preferences') + '#default-project-view', target: '_blank')
- .col-sm-10
- = f.select :project_view, project_view_choices, {}, class: 'form-control'
- .help-block
- Choose what content you want to see on a project's home page.
- .panel-footer
+ %p
+ This setting allows you to customize the behavior of the system layout and default views.
+ .col-lg-9
+ .form-group
+ = f.label :layout, class: 'label-light' do
+ Layout width
+ = f.select :layout, layout_choices, {}, class: 'form-control'
+ .help-block
+ Choose between fixed (max. 1200px) and fluid (100%) application layout.
+ .form-group
+ = f.label :dashboard, class: 'label-light' do
+ Default Dashboard
+ = link_to('(?)', help_page_path('profile', 'preferences') + '#default-dashboard', target: '_blank')
+ = f.select :dashboard, dashboard_choices, {}, class: 'form-control'
+ .form-group
+ = f.label :project_view, class: 'label-light' do
+ Project view
+ = link_to('(?)', help_page_path('profile', 'preferences') + '#default-project-view', target: '_blank')
+ = f.select :project_view, project_view_choices, {}, class: 'form-control'
+ .help-block
+ Choose what content you want to see on a project's home page.
+ .form-group
= f.submit 'Save changes', class: 'btn btn-save'
diff --git a/app/views/profiles/show.html.haml b/app/views/profiles/show.html.haml
index add9a00138b..3d1ba49491c 100644
--- a/app/views/profiles/show.html.haml
+++ b/app/views/profiles/show.html.haml
@@ -1,101 +1,115 @@
-.gray-content-block.top-block
- This information will appear on your profile.
- - if current_user.ldap_user?
- Some options are unavailable for LDAP accounts
-
-.prepend-top-default
-= form_for @user, url: profile_path, method: :put, html: { multipart: true, class: "edit_user form-horizontal" }, authenticity_token: true do |f|
+= form_for @user, url: profile_path, method: :put, html: { multipart: true, class: "edit-user prepend-top-default" }, authenticity_token: true do |f|
+ = f.hidden_field :avatar_crop_x
+ = f.hidden_field :avatar_crop_y
+ = f.hidden_field :avatar_crop_size
-if @user.errors.any?
%div.alert.alert-danger
%ul
- @user.errors.full_messages.each do |msg|
%li= msg
.row
- .col-md-7
+ .col-lg-3.profile-settings-sidebar
+ %h4.prepend-top-0
+ Public Avatar
+ %p
+ - if @user.avatar?
+ You can change your avatar here
+ - if Gitlab.config.gravatar.enabled
+ or remove the current avatar to revert to #{link_to Gitlab.config.gravatar.host, "http://" + Gitlab.config.gravatar.host}
+ - else
+ You can upload an avatar here
+ - if Gitlab.config.gravatar.enabled
+ or change it at #{link_to Gitlab.config.gravatar.host, "http://" + Gitlab.config.gravatar.host}
+ .col-lg-9
+ .clearfix.avatar-image.append-bottom-default
+ = image_tag avatar_icon(@user, 160), alt: '', class: 'avatar s160'
+ %h5.prepend-top-0
+ Upload new avatar
+ .prepend-top-5.append-bottom-10
+ %a.btn.js-choose-user-avatar-button
+ Browse file...
+ %span.avatar-file-name.prepend-left-default.js-avatar-filename No file chosen
+ = f.file_field :avatar, class: "js-user-avatar-input hidden"
+ .help-block
+ The maximum file size allowed is 200KB.
+ - if @user.avatar?
+ %hr
+ = link_to 'Remove avatar', profile_avatar_path, data: { confirm: "Avatar will be removed. Are you sure?"}, method: :delete, class: "btn btn-gray"
+ %hr
+ .row
+ .col-lg-3.profile-settings-sidebar
+ %h4.prepend-top-0
+ Main settings
+ %p
+ This information will appear on your profile.
+ - if current_user.ldap_user?
+ Some options are unavailable for LDAP accounts
+ .col-lg-9
.form-group
- = f.label :name, class: "control-label"
- .col-sm-10
- = f.text_field :name, class: "form-control", required: true
- %span.help-block Enter your name, so people you know can recognize you.
+ = f.label :name, class: "label-light"
+ = f.text_field :name, class: "form-control", required: true
+ %span.help-block Enter your name, so people you know can recognize you.
.form-group
- = f.label :email, class: "control-label"
- .col-sm-10
- - if @user.ldap_user? && @user.ldap_email?
- = f.text_field :email, class: "form-control", required: true, readonly: true
- %span.help-block.light
- Your email address was automatically set based on the LDAP server.
+ = f.label :email, class: "label-light"
+ - if @user.ldap_user? && @user.ldap_email?
+ = f.text_field :email, class: "form-control", required: true, readonly: true
+ %span.help-block.light
+ Your email address was automatically set based on the LDAP server.
+ - else
+ - if @user.temp_oauth_email?
+ = f.text_field :email, class: "form-control", required: true, value: nil
- else
- - if @user.temp_oauth_email?
- = f.text_field :email, class: "form-control", required: true, value: nil
- - else
- = f.text_field :email, class: "form-control", required: true
- - if @user.unconfirmed_email.present?
- %span.help-block
- Please click the link in the confirmation email before continuing. It was sent to
- = succeed "." do
- %strong #{@user.unconfirmed_email}
- %p
- = link_to "Resend confirmation e-mail", user_confirmation_path(user: { email: @user.unconfirmed_email }), method: :post
+ = f.text_field :email, class: "form-control", required: true
+ - if @user.unconfirmed_email.present?
+ %span.help-block
+ Please click the link in the confirmation email before continuing. It was sent to
+ = succeed "." do
+ %strong #{@user.unconfirmed_email}
+ %p
+ = link_to "Resend confirmation e-mail", user_confirmation_path(user: { email: @user.unconfirmed_email }), method: :post
- - else
- %span.help-block We also use email for avatar detection if no avatar is uploaded.
+ - else
+ %span.help-block We also use email for avatar detection if no avatar is uploaded.
.form-group
- = f.label :public_email, class: "control-label"
- .col-sm-10
- = f.select :public_email, options_for_select(@user.all_emails, selected: @user.public_email), {include_blank: 'Do not show on profile'}, class: "select2"
- %span.help-block This email will be displayed on your public profile.
+ = f.label :public_email, class: "label-light"
+ = f.select :public_email, options_for_select(@user.all_emails, selected: @user.public_email), {include_blank: 'Do not show on profile'}, class: "select2"
+ %span.help-block This email will be displayed on your public profile.
.form-group
- = f.label :skype, class: "control-label"
- .col-sm-10= f.text_field :skype, class: "form-control"
+ = f.label :skype, class: "label-light"
+ = f.text_field :skype, class: "form-control"
.form-group
- = f.label :linkedin, class: "control-label"
- .col-sm-10= f.text_field :linkedin, class: "form-control"
+ = f.label :linkedin, class: "label-light"
+ = f.text_field :linkedin, class: "form-control"
.form-group
- = f.label :twitter, class: "control-label"
- .col-sm-10= f.text_field :twitter, class: "form-control"
+ = f.label :twitter, class: "label-light"
+ = f.text_field :twitter, class: "form-control"
.form-group
- = f.label :website_url, 'Website', class: "control-label"
- .col-sm-10= f.text_field :website_url, class: "form-control"
+ = f.label :website_url, 'Website', class: "label-light"
+ = f.text_field :website_url, class: "form-control"
.form-group
- = f.label :location, 'Location', class: "control-label"
- .col-sm-10= f.text_field :location, class: "form-control"
+ = f.label :location, 'Location', class: "label-light"
+ = f.text_field :location, class: "form-control"
.form-group
- = f.label :bio, class: "control-label"
- .col-sm-10
- = f.text_area :bio, rows: 4, class: "form-control", maxlength: 250
- %span.help-block Tell us about yourself in fewer than 250 characters.
-
- .col-md-5
- .light-well
- = image_tag avatar_icon(@user, 160), alt: '', class: 'avatar s160'
-
- .clearfix
- .profile-avatar-form-option
- %p.light
- - if @user.avatar?
- You can change your avatar here
- - if Gitlab.config.gravatar.enabled
- %br
- or remove the current avatar to revert to #{link_to Gitlab.config.gravatar.host, "http://" + Gitlab.config.gravatar.host}
- - else
- You can upload an avatar here
- - if Gitlab.config.gravatar.enabled
- %br
- or change it at #{link_to Gitlab.config.gravatar.host, "http://" + Gitlab.config.gravatar.host}
- %hr
- %a.choose-btn.btn.btn-sm.js-choose-user-avatar-button
- %i.fa.fa-paperclip
- %span Choose File ...
- &nbsp;
- %span.file_name.js-avatar-filename File name...
- = f.file_field :avatar, class: "js-user-avatar-input hidden"
- .light The maximum file size allowed is 200KB.
- - if @user.avatar?
- %hr
- = link_to 'Remove avatar', profile_avatar_path, data: { confirm: "Avatar will be removed. Are you sure?"}, method: :delete, class: "btn btn-remove btn-sm remove-avatar"
-
+ = f.label :bio, class: "label-light"
+ = f.text_area :bio, rows: 4, class: "form-control", maxlength: 250
+ %span.help-block Tell us about yourself in fewer than 250 characters.
+ .prepend-top-default.append-bottom-default
+ = f.submit 'Update profile settings', class: "btn btn-success"
+ = link_to "Cancel", user_path(current_user), class: "btn btn-cancel"
- .form-actions
- = f.submit 'Save changes', class: "btn btn-success"
- = link_to "Cancel", user_path(current_user), class: "btn btn-cancel"
+.modal.modal-profile-crop
+ .modal-dialog
+ .modal-content
+ .modal-header
+ %button.close{type: 'button', data: {dismiss: 'modal'}}
+ %span
+ &times;
+ %h4.modal-title
+ Crop your new profile picture
+ .modal-body
+ %p
+ %img.modal-profile-crop-image
+ .modal-footer
+ %button.btn.btn-primary.js-upload-user-avatar{:type => "button"}
+ Set new profile picture
diff --git a/app/views/projects/_home_panel.html.haml b/app/views/projects/_home_panel.html.haml
index 298c6664997..b45df44f270 100644
--- a/app/views/projects/_home_panel.html.haml
+++ b/app/views/projects/_home_panel.html.haml
@@ -3,7 +3,12 @@
.project-identicon-holder
= project_icon(@project, alt: '', class: 'project-avatar avatar s90')
.project-home-desc
- %h1= @project.name
+ %h1
+ = @project.name
+ %span.visibility-icon.has_tooltip{data: { container: 'body' },
+ title: "#{visibility_level_label(@project.visibility_level)} - #{project_visibility_level_description(@project.visibility_level)}"}
+ = visibility_level_icon(@project.visibility_level, fw: false)
+
- if @project.description.present?
= markdown(@project.description, pipeline: :description)
@@ -12,10 +17,6 @@
Forked from
= link_to project_path(forked_from_project) do
= forked_from_project.namespace.try(:name)
- .cover-controls.left
- .visibility-level-label.has_tooltip{title: project_visibility_level_description(@project.visibility_level), data: { container: 'body' } }
- = visibility_level_icon(@project.visibility_level, fw: false)
- = visibility_level_label(@project.visibility_level)
.cover-controls
- if current_user
diff --git a/app/views/projects/blame/show.html.haml b/app/views/projects/blame/show.html.haml
index eb6fbfaffa0..5f9a92ff93f 100644
--- a/app/views/projects/blame/show.html.haml
+++ b/app/views/projects/blame/show.html.haml
@@ -3,7 +3,7 @@
%h3.page-title Blame view
-#tree-holder.tree-holder
+#blob-content-holder.tree-holder
.file-holder
.file-title
= blob_icon @blob.mode, @blob.name
@@ -33,7 +33,9 @@
%td.line-numbers
- line_count = blame_group[:lines].count
- (current_line...(current_line + line_count)).each do |i|
- %a.diff-line-num= i
+ %a.diff-line-num{href: "#L#{i}", id: "L#{i}", 'data-line-number' => i}
+ = icon("link")
+ = i
\
- current_line += line_count
%td.lines
diff --git a/app/views/projects/blob/_blob.html.haml b/app/views/projects/blob/_blob.html.haml
index 2c5b8dc4356..3ffc3fcb7ac 100644
--- a/app/views/projects/blob/_blob.html.haml
+++ b/app/views/projects/blob/_blob.html.haml
@@ -32,14 +32,4 @@
= number_to_human_size(blob_size(blob))
.file-actions.hidden-xs
= render "actions"
- - if blob.lfs_pointer?
- = render "download", blob: blob
- - elsif blob.text?
- - if blob_svg?(blob)
- = render "image", blob: sanitize_svg(blob)
- - else
- = render "text", blob: blob
- - elsif blob.image?
- = render "image", blob: blob
- - else
- = render "download", blob: blob
+ = render blob, blob: blob
diff --git a/app/views/projects/blob/_editor.html.haml b/app/views/projects/blob/_editor.html.haml
index 10b02813733..f8b6fa253c4 100644
--- a/app/views/projects/blob/_editor.html.haml
+++ b/app/views/projects/blob/_editor.html.haml
@@ -10,7 +10,7 @@
%span.editor-file-name
\/
= text_field_tag 'file_name', params[:file_name], placeholder: "File name",
- required: true, class: 'form-control new-file-name js-quick-submit'
+ required: true, class: 'form-control new-file-name'
.pull-right
= select_tag :encoding, options_for_select([ "base64", "text" ], "text"), class: 'select2'
diff --git a/app/views/projects/blob/_image.html.haml b/app/views/projects/blob/_image.html.haml
index 51fa91b08e4..18caddabd39 100644
--- a/app/views/projects/blob/_image.html.haml
+++ b/app/views/projects/blob/_image.html.haml
@@ -1,2 +1,9 @@
.file-content.image_file
- %img{ src: namespace_project_raw_path(@project.namespace, @project, @id)}
+ - if blob.svg?
+ - # We need to scrub SVG but we cannot do so in the RawController: it would
+ - # be wrong/strange if RawController modified the data.
+ - blob.load_all_data!(@repository)
+ - blob = sanitize_svg(blob)
+ %img{src: "data:#{blob.mime_type};base64,#{Base64.encode64(blob.data)}"}
+ - else
+ %img{src: namespace_project_raw_path(@project.namespace, @project, tree_join(@commit.id, blob.path))}
diff --git a/app/views/projects/blob/_new_dir.html.haml b/app/views/projects/blob/_new_dir.html.haml
index 084608bbba3..84694203d7d 100644
--- a/app/views/projects/blob/_new_dir.html.haml
+++ b/app/views/projects/blob/_new_dir.html.haml
@@ -5,7 +5,7 @@
%a.close{href: "#", "data-dismiss" => "modal"} ×
%h3.page-title Create New Directory
.modal-body
- = form_tag namespace_project_create_dir_path(@project.namespace, @project, @id), method: :post, remote: false, class: 'form-horizontal js-create-dir-form js-requires-input' do
+ = form_tag namespace_project_create_dir_path(@project.namespace, @project, @id), method: :post, remote: false, class: 'form-horizontal js-create-dir-form js-quick-submit js-requires-input' do
.form-group
= label_tag :dir_name, 'Directory name', class: 'control-label'
.col-sm-10
diff --git a/app/views/projects/blob/_remove.html.haml b/app/views/projects/blob/_remove.html.haml
index 1cf19a7d3db..2e1f32fd15e 100644
--- a/app/views/projects/blob/_remove.html.haml
+++ b/app/views/projects/blob/_remove.html.haml
@@ -6,7 +6,7 @@
%h3.page-title Delete #{@blob.name}
.modal-body
- = form_tag namespace_project_blob_path(@project.namespace, @project, @id), method: :delete, class: 'form-horizontal js-replace-blob-form js-requires-input' do
+ = form_tag namespace_project_blob_path(@project.namespace, @project, @id), method: :delete, class: 'form-horizontal js-replace-blob-form js-quick-submit js-requires-input' do
= render 'shared/new_commit_form', placeholder: "Delete #{@blob.name}"
.form-group
diff --git a/app/views/projects/blob/_upload.html.haml b/app/views/projects/blob/_upload.html.haml
index 676924dc6ca..b1f50eb5f34 100644
--- a/app/views/projects/blob/_upload.html.haml
+++ b/app/views/projects/blob/_upload.html.haml
@@ -5,7 +5,7 @@
%a.close{href: "#", "data-dismiss" => "modal"} ×
%h3.page-title #{title}
.modal-body
- = form_tag form_path, method: method, class: 'js-upload-blob-form form-horizontal' do
+ = form_tag form_path, method: method, class: 'js-quick-submit js-upload-blob-form form-horizontal' do
.dropzone
.dropzone-previews.blob-upload-dropzone-previews
%p.dz-message.light
diff --git a/app/views/projects/blob/edit.html.haml b/app/views/projects/blob/edit.html.haml
index a279e6eda55..effcce5a1c4 100644
--- a/app/views/projects/blob/edit.html.haml
+++ b/app/views/projects/blob/edit.html.haml
@@ -13,7 +13,7 @@
= icon('eye')
= editing_preview_title(@blob.name)
- = form_tag(namespace_project_update_blob_path(@project.namespace, @project, @id), method: :put, class: 'form-horizontal js-requires-input js-edit-blob-form') do
+ = form_tag(namespace_project_update_blob_path(@project.namespace, @project, @id), method: :put, class: 'form-horizontal js-quick-submit js-requires-input js-edit-blob-form') do
= render 'projects/blob/editor', ref: @ref, path: @path, blob_data: @blob.data
= render 'shared/new_commit_form', placeholder: "Update #{@blob.name}"
diff --git a/app/views/projects/blob/new.html.haml b/app/views/projects/blob/new.html.haml
index 167fa615182..1dd2b5c0af7 100644
--- a/app/views/projects/blob/new.html.haml
+++ b/app/views/projects/blob/new.html.haml
@@ -5,7 +5,7 @@
New File
.file-editor
- = form_tag(namespace_project_create_blob_path(@project.namespace, @project, @id), method: :post, class: 'form-horizontal js-new-blob-form js-requires-input') do
+ = form_tag(namespace_project_create_blob_path(@project.namespace, @project, @id), method: :post, class: 'form-horizontal js-new-blob-form js-quick-submit js-requires-input') do
= render 'projects/blob/editor', ref: @ref
= render 'shared/new_commit_form', placeholder: "Add new file"
diff --git a/app/views/projects/builds/index.html.haml b/app/views/projects/builds/index.html.haml
index 5e3bd14565e..14f1d3226bb 100644
--- a/app/views/projects/builds/index.html.haml
+++ b/app/views/projects/builds/index.html.haml
@@ -51,9 +51,11 @@
%th Name
%th Duration
%th Finished at
+ - if @project.build_coverage_enabled?
+ %th Coverage
%th
- @builds.each do |build|
- = render 'projects/commit_statuses/commit_status', commit_status: build, commit_sha: true, stage: true, allow_retry: true
+ = render 'projects/commit_statuses/commit_status', commit_status: build, commit_sha: true, stage: true, coverage: @project.build_coverage_enabled?, allow_retry: true
= paginate @builds, theme: 'gitlab'
diff --git a/app/views/projects/builds/show.html.haml b/app/views/projects/builds/show.html.haml
index ca1441a20d8..8eec78a557c 100644
--- a/app/views/projects/builds/show.html.haml
+++ b/app/views/projects/builds/show.html.haml
@@ -76,10 +76,16 @@
= link_to '#down-build-trace', class: 'btn' do
%i.fa.fa-angle-down
- %pre.trace#build-trace
- %code.bash
- = preserve do
- = raw @build.trace_html
+ - if @build.erased?
+ .erased.alert.alert-warning
+ - erased_by = "by #{link_to @build.erased_by.name, user_path(@build.erased_by)}" if @build.erased_by
+ Build has been erased #{erased_by.html_safe} #{time_ago_with_tooltip(@build.erased_at)}
+ - else
+ %pre.trace#build-trace
+ %code.bash
+ = preserve do
+ = raw @build.trace_html
+
%div#down-build-trace
.col-md-3
@@ -94,37 +100,55 @@
%h4.title Build artifacts
.center
.btn-group{ role: :group }
- = link_to "Download", @build.artifacts_download_url, class: 'btn btn-sm btn-primary'
+ = link_to @build.artifacts_download_url, class: 'btn btn-sm btn-primary' do
+ = icon('download')
+ Download
+
- if @build.artifacts_metadata?
- = link_to "Browse", @build.artifacts_browse_url, class: 'btn btn-sm btn-primary'
+ = link_to @build.artifacts_browse_url, class: 'btn btn-sm btn-primary' do
+ = icon('folder-open')
+ Browse
.build-widget
%h4.title
Build ##{@build.id}
- if can?(current_user, :update_build, @project)
- .pull-right
- - if @build.cancel_url
- = link_to "Cancel", @build.cancel_url, class: 'btn btn-sm btn-danger', method: :post
- - elsif @build.retry_url
- = link_to "Retry", @build.retry_url, class: 'btn btn-sm btn-primary', method: :post
-
- - if @build.duration
+ .center
+ .btn-group{ role: :group }
+ - if @build.cancel_url
+ = link_to "Cancel", @build.cancel_url, class: 'btn btn-sm btn-danger', method: :post
+ - elsif @build.retry_url
+ = link_to "Retry", @build.retry_url, class: 'btn btn-sm btn-primary', method: :post
+
+ - if @build.erasable?
+ = link_to erase_namespace_project_build_path(@project.namespace, @project, @build),
+ class: 'btn btn-sm btn-warning', method: :post,
+ data: { confirm: 'Are you sure you want to erase this build?' } do
+ = icon('eraser')
+ Erase
+
+ .clearfix
+ - if @build.duration
+ %p
+ %span.attr-name Duration:
+ #{duration_in_words(@build.finished_at, @build.started_at)}
%p
- %span.attr-name Duration:
- #{duration_in_words(@build.finished_at, @build.started_at)}
- %p
- %span.attr-name Created:
- #{time_ago_with_tooltip(@build.created_at)}
- - if @build.finished_at
+ %span.attr-name Created:
+ #{time_ago_with_tooltip(@build.created_at)}
+ - if @build.finished_at
+ %p
+ %span.attr-name Finished:
+ #{time_ago_with_tooltip(@build.finished_at)}
+ - if @build.erased_at
+ %p
+ %span.attr-name Erased:
+ #{time_ago_with_tooltip(@build.erased_at)}
%p
- %span.attr-name Finished:
- #{time_ago_with_tooltip(@build.finished_at)}
- %p
- %span.attr-name Runner:
- - if @build.runner && current_user && current_user.admin
- = link_to "##{@build.runner.id}", admin_runner_path(@build.runner.id)
- - elsif @build.runner
- \##{@build.runner.id}
+ %span.attr-name Runner:
+ - if @build.runner && current_user && current_user.admin
+ = link_to "##{@build.runner.id}", admin_runner_path(@build.runner.id)
+ - elsif @build.runner
+ \##{@build.runner.id}
- if @build.trigger_request
.build-widget
diff --git a/app/views/projects/commit/_commit_box.html.haml b/app/views/projects/commit/_commit_box.html.haml
index bbe820b8842..71995fcc487 100644
--- a/app/views/projects/commit/_commit_box.html.haml
+++ b/app/views/projects/commit/_commit_box.html.haml
@@ -16,6 +16,8 @@
= link_to namespace_project_tree_path(@project.namespace, @project, @commit), class: "btn btn-grouped" do
= icon('files-o')
Browse Files
+ - unless @commit.has_been_reverted?(current_user)
+ = revert_commit_link(@commit, namespace_project_commit_path(@project.namespace, @project, @commit.id))
%div
%p
diff --git a/app/views/projects/commit/_revert.html.haml b/app/views/projects/commit/_revert.html.haml
new file mode 100644
index 00000000000..52ca3ed5b14
--- /dev/null
+++ b/app/views/projects/commit/_revert.html.haml
@@ -0,0 +1,31 @@
+#modal-revert-commit.modal
+ .modal-dialog
+ .modal-content
+ .modal-header
+ %a.close{href: "#", "data-dismiss" => "modal"} ×
+ %h3.page-title== Revert this #{revert_commit_type(commit)}
+ .modal-body
+ = form_tag revert_namespace_project_commit_path(@project.namespace, @project, commit.id), method: :post, remote: false, class: 'form-horizontal js-create-dir-form js-requires-input' do
+ .form-group.branch
+ = label_tag 'target_branch', 'Revert in branch', class: 'control-label'
+ .col-sm-10
+ = select_tag "target_branch", grouped_options_refs, class: "select2 select2-sm js-target-branch"
+ - if can?(current_user, :push_code, @project)
+ .js-create-merge-request-container
+ .checkbox
+ - nonce = SecureRandom.hex
+ = label_tag "create_merge_request-#{nonce}" do
+ = check_box_tag 'create_merge_request', 1, true, class: 'js-create-merge-request', id: "create_merge_request-#{nonce}"
+ Start a <strong>new merge request</strong> with these changes
+ - else
+ = hidden_field_tag 'create_merge_request', 1
+ .form-actions
+ = submit_tag "Revert", class: 'btn btn-create'
+ = link_to "Cancel", '#', class: "btn btn-cancel", "data-dismiss" => "modal"
+
+ - unless can?(current_user, :push_code, @project)
+ .inline.prepend-left-10
+ = commit_in_fork_help
+
+:javascript
+ new NewCommitForm($('.js-create-dir-form'))
diff --git a/app/views/projects/commit/show.html.haml b/app/views/projects/commit/show.html.haml
index 05dbe5ebea4..21e186120c3 100644
--- a/app/views/projects/commit/show.html.haml
+++ b/app/views/projects/commit/show.html.haml
@@ -12,3 +12,5 @@
= render "projects/diffs/diffs", diffs: @diffs, project: @project,
diff_refs: @diff_refs
= render "projects/notes/notes_with_form"
+- if can_collaborate_with_project?
+ = render "projects/commit/revert", commit: @commit, title: @commit.title
diff --git a/app/views/projects/diffs/_diffs.html.haml b/app/views/projects/diffs/_diffs.html.haml
index d668f483bcb..6086ad3661e 100644
--- a/app/views/projects/diffs/_diffs.html.haml
+++ b/app/views/projects/diffs/_diffs.html.haml
@@ -10,8 +10,8 @@
= parallel_diff_btn
= render 'projects/diffs/stats', diff_files: diff_files
-- if diff_files.count < diffs.size
- = render 'projects/diffs/warning', diffs: diffs, shown_files_count: diff_files.count
+- if diff_files.overflow?
+ = render 'projects/diffs/warning', diff_files: diff_files
.files
- diff_files.each_with_index do |diff_file, index|
@@ -21,10 +21,3 @@
= render 'projects/diffs/file', i: index, project: project,
diff_file: diff_file, diff_commit: diff_commit, blob: blob
-
-- if @diff_timeout
- .alert.alert-danger
- %h4
- Failed to collect changes
- %p
- Maybe diff is really big and operation failed with timeout. Try to get diff locally
diff --git a/app/views/projects/diffs/_image.html.haml b/app/views/projects/diffs/_image.html.haml
index 058b71b21f5..752e92e2e6b 100644
--- a/app/views/projects/diffs/_image.html.haml
+++ b/app/views/projects/diffs/_image.html.haml
@@ -1,9 +1,11 @@
- diff = diff_file.diff
+- file_raw_path = namespace_project_raw_path(@project.namespace, @project, tree_join(@commit.id, diff.new_path))
+- old_file_raw_path = namespace_project_raw_path(@project.namespace, @project, tree_join(@commit.parent_id, diff.old_path))
- if diff.renamed_file || diff.new_file || diff.deleted_file
.image
%span.wrap
.frame{class: image_diff_class(diff)}
- %img{src: "data:#{file.mime_type};base64,#{Base64.encode64(file.data)}"}
+ %img{src: diff.deleted_file ? old_file_raw_path : file_raw_path}
%p.image-info= "#{number_to_human_size file.size}"
- else
.image
@@ -11,7 +13,7 @@
%span.wrap
.frame.deleted
%a{href: namespace_project_blob_path(@project.namespace, @project, tree_join(@commit.parent_id, diff.old_path))}
- %img{src: "data:#{old_file.mime_type};base64,#{Base64.encode64(old_file.data)}"}
+ %img{src: old_file_raw_path}
%p.image-info.hide
%span.meta-filesize= "#{number_to_human_size old_file.size}"
|
@@ -23,7 +25,7 @@
%span.wrap
.frame.added
%a{href: namespace_project_blob_path(@project.namespace, @project, tree_join(@commit.id, diff.new_path))}
- %img{src: "data:#{file.mime_type};base64,#{Base64.encode64(file.data)}"}
+ %img{src: file_raw_path}
%p.image-info.hide
%span.meta-filesize= "#{number_to_human_size file.size}"
|
@@ -36,10 +38,10 @@
%div.swipe.view.hide
.swipe-frame
.frame.deleted
- %img{src: "data:#{old_file.mime_type};base64,#{Base64.encode64(old_file.data)}"}
+ %img{src: old_file_raw_path}
.swipe-wrap
.frame.added
- %img{src: "data:#{file.mime_type};base64,#{Base64.encode64(file.data)}"}
+ %img{src: file_raw_path}
%span.swipe-bar
%span.top-handle
%span.bottom-handle
@@ -47,9 +49,9 @@
%div.onion-skin.view.hide
.onion-skin-frame
.frame.deleted
- %img{src: "data:#{old_file.mime_type};base64,#{Base64.encode64(old_file.data)}"}
+ %img{src: old_file_raw_path}
.frame.added
- %img{src: "data:#{file.mime_type};base64,#{Base64.encode64(file.data)}"}
+ %img{src: file_raw_path}
.controls
.transparent
.drag-track
diff --git a/app/views/projects/diffs/_text_file.html.haml b/app/views/projects/diffs/_text_file.html.haml
index 5e835b10e1f..9a8208202e4 100644
--- a/app/views/projects/diffs/_text_file.html.haml
+++ b/app/views/projects/diffs/_text_file.html.haml
@@ -6,7 +6,7 @@
%table.text-file.code.js-syntax-highlight{ class: too_big ? 'hide' : '' }
- last_line = 0
- - raw_diff_lines = diff_file.diff_lines
+ - raw_diff_lines = diff_file.diff_lines.to_a
- diff_file.highlighted_diff_lines.each_with_index do |line, index|
- type = line.type
- last_line = line.new_pos
@@ -35,8 +35,8 @@
= render "projects/notes/diff_notes_with_reply", notes: comments, line: raw_diff_lines[index].text
- if last_line > 0
- = render "projects/diffs/match_line", {line: "",
- line_old: last_line, line_new: last_line, bottom: true, new_file: diff_file.new_file}
+ = render "projects/diffs/match_line", { line: "",
+ line_old: last_line, line_new: last_line, bottom: true, new_file: diff_file.new_file }
- if diff_file.diff.blank? && diff_file.mode_changed?
.file-mode-changed
diff --git a/app/views/projects/diffs/_warning.html.haml b/app/views/projects/diffs/_warning.html.haml
index f99bc9a85eb..15536c17f8e 100644
--- a/app/views/projects/diffs/_warning.html.haml
+++ b/app/views/projects/diffs/_warning.html.haml
@@ -3,17 +3,16 @@
Too many changes to show.
.pull-right
- unless diff_hard_limit_enabled?
- = link_to "Reload with full diff", url_for(params.merge(force_show_diff: true, format: nil)), class: "btn btn-sm btn-warning"
+ = link_to "Reload with full diff", url_for(params.merge(force_show_diff: true, format: nil)), class: "btn btn-sm"
- if current_controller?(:commit) or current_controller?(:merge_requests)
- if current_controller?(:commit)
- = link_to "Plain diff", namespace_project_commit_path(@project.namespace, @project, @commit, format: :diff), class: "btn btn-warning btn-sm"
- = link_to "Email patch", namespace_project_commit_path(@project.namespace, @project, @commit, format: :patch), class: "btn btn-warning btn-sm"
+ = link_to "Plain diff", namespace_project_commit_path(@project.namespace, @project, @commit, format: :diff), class: "btn btn-sm"
+ = link_to "Email patch", namespace_project_commit_path(@project.namespace, @project, @commit, format: :patch), class: "btn btn-sm"
- elsif @merge_request && @merge_request.persisted?
- = link_to "Plain diff", merge_request_path(@merge_request, format: :diff), class: "btn btn-warning btn-sm"
- = link_to "Email patch", merge_request_path(@merge_request, format: :patch), class: "btn btn-warning btn-sm"
+ = link_to "Plain diff", merge_request_path(@merge_request, format: :diff), class: "btn btn-sm"
+ = link_to "Email patch", merge_request_path(@merge_request, format: :patch), class: "btn btn-sm"
%p
To preserve performance only
- %strong #{shown_files_count} of #{diffs.size}
+ %strong #{diff_files.count} of #{diff_files.real_size}
files are displayed.
-
diff --git a/app/views/projects/edit.html.haml b/app/views/projects/edit.html.haml
index fdcb6987471..f2e56081afe 100644
--- a/app/views/projects/edit.html.haml
+++ b/app/views/projects/edit.html.haml
@@ -1,5 +1,3 @@
-- @blank_container = true
-
.project-edit-container.prepend-top-default
.project-edit-errors
.project-edit-content
@@ -119,13 +117,13 @@
.col-sm-offset-2.col-sm-10
%p Get recent application code using the following command:
.radio
- = f.label :build_allow_git_fetch do
+ = f.label :build_allow_git_fetch_false do
= f.radio_button :build_allow_git_fetch, 'false'
%strong git clone
%br
%span.descr Slower but makes sure you have a clean dir before every build
.radio
- = f.label :build_allow_git_fetch do
+ = f.label :build_allow_git_fetch_true do
= f.radio_button :build_allow_git_fetch, 'true'
%strong git fetch
%br
diff --git a/app/views/projects/empty.html.haml b/app/views/projects/empty.html.haml
index b34d106d565..6ad7b05155a 100644
--- a/app/views/projects/empty.html.haml
+++ b/app/views/projects/empty.html.haml
@@ -18,7 +18,7 @@
= link_to "adding README", new_readme_path, class: 'underlined-link'
file to this project.
-- if can?(current_user, :download_code, @project)
+- if can?(current_user, :push_code, @project)
%div{ class: container_class }
.prepend-top-20
.empty_wrapper
diff --git a/app/views/projects/forks/_projects.html.haml b/app/views/projects/forks/_projects.html.haml
new file mode 100644
index 00000000000..2946e6dcbd0
--- /dev/null
+++ b/app/views/projects/forks/_projects.html.haml
@@ -0,0 +1,2 @@
+= render 'shared/projects/list', projects: projects, use_creator_avatar: true,
+ forks: true, show_last_commit_as_description: true
diff --git a/app/views/projects/forks/index.html.haml b/app/views/projects/forks/index.html.haml
index 42fa6fdb782..4bcf2d9d533 100644
--- a/app/views/projects/forks/index.html.haml
+++ b/app/views/projects/forks/index.html.haml
@@ -1,13 +1,12 @@
.top-area
.nav-text
- - public_count = @public_forks.size
- - protected_count = @protected_forks.size
- - full_count_title = "#{public_count} public and #{protected_count} private"
- == #{pluralize(@all_forks.size, 'fork')}: #{full_count_title}
+ - full_count_title = "#{@public_forks_count} public and #{@private_forks_count} private"
+ == #{pluralize(@total_forks_count, 'fork')}: #{full_count_title}
.nav-controls
- = search_field_tag :filter_projects, nil, placeholder: 'Search forks', class: 'projects-list-filter project-filter-form-field form-control input-short',
- spellcheck: false, data: { 'filter-selector' => 'span.namespace-name' }
+ = form_tag request.original_url, method: :get, class: 'project-filter-form', id: 'project-filter-form' do |f|
+ = search_field_tag :filter_projects, nil, placeholder: 'Search forks', class: 'projects-list-filter project-filter-form-field form-control input-short',
+ spellcheck: false, data: { 'filter-selector' => 'span.namespace-name' }
.dropdown
%button.dropdown-toggle.btn.sort-forks{type: 'button', 'data-toggle' => 'dropdown'}
@@ -40,18 +39,10 @@
Fork
-.projects-list-holder
- - if @public_forks.blank?
- %ul.content-list
- %li
- .nothing-here-block No forks to show
- - else
- = render 'shared/projects/list', projects: @public_forks, use_creator_avatar: true,
- forks: true, show_last_commit_as_description: true
+= render 'projects', projects: @forks
- - if protected_count > 0
- %ul.projects-list.private-forks-notice
- %li.project-row
- = icon('lock fw', base: 'circle', class: 'fa-lg private-fork-icon')
- %strong= pluralize(protected_count, 'private fork')
- %span you have no access to.
+- if @private_forks_count > 0
+ .private-forks-notice
+ = icon('lock fw', base: 'circle', class: 'fa-lg private-fork-icon')
+ %strong= pluralize(@private_forks_count, 'private fork')
+ %span you have no access to.
diff --git a/app/views/projects/issues/_discussion.html.haml b/app/views/projects/issues/_discussion.html.haml
index 673020a4e30..eb9c225df2f 100644
--- a/app/views/projects/issues/_discussion.html.haml
+++ b/app/views/projects/issues/_discussion.html.haml
@@ -1,7 +1,7 @@
- content_for :note_actions do
- if can?(current_user, :update_issue, @issue)
- = link_to 'Reopen Issue', issue_path(@issue, issue: {state_event: :reopen}, status_only: true, format: 'json'), data: {no_turbolink: true}, class: "btn btn-nr btn-grouped btn-reopen btn-comment js-note-target-reopen #{issue_button_visibility(@issue, false)}", title: 'Reopen Issue'
- = link_to 'Close Issue', issue_path(@issue, issue: {state_event: :close}, status_only: true, format: 'json'), data: {no_turbolink: true}, class: "btn btn-nr btn-grouped btn-close btn-comment js-note-target-close #{issue_button_visibility(@issue, true)}", title: 'Close Issue'
+ = link_to 'Reopen issue', issue_path(@issue, issue: {state_event: :reopen}, status_only: true, format: 'json'), data: {no_turbolink: true}, class: "btn btn-nr btn-grouped btn-reopen btn-comment js-note-target-reopen #{issue_button_visibility(@issue, false)}", title: 'Reopen issue'
+ = link_to 'Close issue', issue_path(@issue, issue: {state_event: :close}, status_only: true, format: 'json'), data: {no_turbolink: true}, class: "btn btn-nr btn-grouped btn-close btn-comment js-note-target-close #{issue_button_visibility(@issue, true)}", title: 'Close issue'
#notes
= render 'projects/notes/notes_with_form'
diff --git a/app/views/projects/issues/_form.html.haml b/app/views/projects/issues/_form.html.haml
index 6588d9bdbe1..33c48199ba5 100644
--- a/app/views/projects/issues/_form.html.haml
+++ b/app/views/projects/issues/_form.html.haml
@@ -1,4 +1,4 @@
-= form_for [@project.namespace.becomes(Namespace), @project, @issue], html: { class: 'form-horizontal issue-form gfm-form js-requires-input' } do |f|
+= form_for [@project.namespace.becomes(Namespace), @project, @issue], html: { class: 'form-horizontal issue-form gfm-form js-quick-submit js-requires-input' } do |f|
= render 'shared/issuable/form', f: f, issuable: @issue
:javascript
diff --git a/app/views/projects/issues/_issue.html.haml b/app/views/projects/issues/_issue.html.haml
index f9cf4910df3..a44f34c2a68 100644
--- a/app/views/projects/issues/_issue.html.haml
+++ b/app/views/projects/issues/_issue.html.haml
@@ -5,7 +5,7 @@
.issue-title
%span.issue-title-text
- = link_to_gfm issue.title, issue_path(issue), class: "row_title"
+ = link_to_gfm issue.title, issue_path(issue), class: "title"
%ul.controls.light
- if issue.closed?
%li
@@ -15,6 +15,17 @@
%li
= link_to_member(@project, issue.assignee, name: false, title: "Assigned to :name")
+ - upvotes, downvotes = issue.upvotes, issue.downvotes
+ - if upvotes > 0
+ %li
+ = icon('thumbs-up')
+ = upvotes
+
+ - if downvotes > 0
+ %li
+ = icon('thumbs-down')
+ = downvotes
+
- note_count = issue.notes.user.count
- if note_count > 0
%li
diff --git a/app/views/projects/issues/show.html.haml b/app/views/projects/issues/show.html.haml
index fe977fd700c..617b0437807 100644
--- a/app/views/projects/issues/show.html.haml
+++ b/app/views/projects/issues/show.html.haml
@@ -5,30 +5,52 @@
= render "header_title"
.issue
- .detail-page-header
- .status-box{ class: "status-box-closed #{issue_button_visibility(@issue, false)}"} Closed
- .status-box{ class: "status-box-open #{issue_button_visibility(@issue, true)}"} Open
- %span.identifier
- Issue ##{@issue.iid}
- %span.creator
- &middot;
- opened by #{link_to_member(@project, @issue.author, size: 24)}
- &middot;
- = time_ago_with_tooltip(@issue.created_at, placement: 'bottom', html_class: 'issue_created_ago')
-
- .pull-right
+ .detail-page-header.issuable-header
+ .pull-left
+ .status-box{ class: "status-box-closed #{issue_button_visibility(@issue, false)}"}
+ %span.hidden-xs
+ Closed
+ %span.hidden-sm.hidden-md.hidden-lg
+ = icon('check')
+ .status-box{ class: "status-box-open #{issue_button_visibility(@issue, true)}"}
+ %span.hidden-xs
+ Open
+ %span.hidden-sm.hidden-md.hidden-lg
+ = icon('circle-o')
+
+ %a.btn.btn-default.pull-right.hidden-sm.hidden-md.hidden-lg.gutter-toggle{ href: "#" }
+ = icon('angle-double-left')
+
+ .issue-meta
+ %strong.identifier
+ Issue ##{@issue.iid}
+ %span.creator
+ by
+ .editor-details
+ .editor-details
+ %strong
+ = link_to_member(@project, @issue.author, size: 24, mobile_classes: "hidden-xs")
+ %span.hidden-xs
+ = '@' + @issue.author.username
+ %strong
+ = link_to_member(@project, @issue.author, size: 24, mobile_classes: "hidden-sm hidden-md hidden-lg",
+ by_username: true, avatar: false)
+ = time_ago_with_tooltip(@issue.created_at)
+
+ .pull-right.issue-btn-group
- if can?(current_user, :create_issue, @project)
- = link_to new_namespace_project_issue_path(@project.namespace, @project), class: 'btn btn-nr btn-grouped new-issue-link btn-success', title: 'New Issue', id: 'new_issue_link' do
+ = link_to new_namespace_project_issue_path(@project.namespace, @project), class: 'btn btn-nr btn-grouped new-issue-link btn-success', title: 'New issue', id: 'new_issue_link' do
= icon('plus')
- New Issue
+ New issue
- if can?(current_user, :update_issue, @issue)
- = link_to 'Reopen', issue_path(@issue, issue: {state_event: :reopen}, status_only: true, format: 'json'), data: {no_turbolink: true}, class: "btn btn-nr btn-grouped btn-reopen #{issue_button_visibility(@issue, false)}", title: 'Reopen Issue'
- = link_to 'Close', issue_path(@issue, issue: {state_event: :close}, status_only: true, format: 'json'), data: {no_turbolink: true}, class: "btn btn-nr btn-grouped btn-close #{issue_button_visibility(@issue, true)}", title: 'Close Issue'
+ = link_to 'Reopen issue', issue_path(@issue, issue: {state_event: :reopen}, status_only: true, format: 'json'), data: {no_turbolink: true}, class: "btn btn-nr btn-grouped btn-reopen #{issue_button_visibility(@issue, false)}", title: 'Reopen issue'
+ = link_to 'Close issue', issue_path(@issue, issue: {state_event: :close}, status_only: true, format: 'json'), data: {no_turbolink: true}, class: "btn btn-nr btn-grouped btn-close #{issue_button_visibility(@issue, true)}", title: 'Close issue'
= link_to edit_namespace_project_issue_path(@project.namespace, @project, @issue), class: 'btn btn-nr btn-grouped issuable-edit' do
= icon('pencil-square-o')
Edit
+
.issue-details.issuable-details
.detail-page-description.content-block
%h2.title
diff --git a/app/views/projects/issues/update.js.haml b/app/views/projects/issues/update.js.haml
index a54733883b4..986d8c220db 100644
--- a/app/views/projects/issues/update.js.haml
+++ b/app/views/projects/issues/update.js.haml
@@ -1,3 +1,3 @@
$('aside.right-sidebar')[0].outerHTML = "#{escape_javascript(render 'shared/issuable/sidebar', issuable: @issue)}";
$('aside.right-sidebar').effect('highlight');
-new Issue(); \ No newline at end of file
+new IssuableContext();
diff --git a/app/views/projects/labels/_form.html.haml b/app/views/projects/labels/_form.html.haml
index 5ce2a7b985d..be7a0bb5628 100644
--- a/app/views/projects/labels/_form.html.haml
+++ b/app/views/projects/labels/_form.html.haml
@@ -1,4 +1,4 @@
-= form_for [@project.namespace.becomes(Namespace), @project, @label], html: { class: 'form-horizontal label-form js-requires-input' } do |f|
+= form_for [@project.namespace.becomes(Namespace), @project, @label], html: { class: 'form-horizontal label-form js-quick-submit js-requires-input' } do |f|
-if @label.errors.any?
.row
.col-sm-offset-2.col-sm-10
@@ -10,7 +10,11 @@
.form-group
= f.label :title, class: 'control-label'
.col-sm-10
- = f.text_field :title, class: "form-control js-quick-submit", required: true, autofocus: true
+ = f.text_field :title, class: "form-control", required: true, autofocus: true
+ .form-group
+ = f.label :description, class: 'control-label'
+ .col-sm-10
+ = f.text_field :description, class: "form-control js-quick-submit"
.form-group
= f.label :color, "Background color", class: 'control-label'
.col-sm-10
diff --git a/app/views/projects/labels/_label.html.haml b/app/views/projects/labels/_label.html.haml
index b70a9fc9fe5..f7ddd30c5a9 100644
--- a/app/views/projects/labels/_label.html.haml
+++ b/app/views/projects/labels/_label.html.haml
@@ -1,7 +1,12 @@
%li{id: dom_id(label)}
- = link_to_label(label)
+ = render "shared/label_row", label: label
+
.pull-right
%strong.append-right-20
+ = link_to_label(label, type: :merge_request) do
+ = pluralize label.open_merge_requests_count, 'open merge request'
+
+ %strong.append-right-20
= link_to_label(label) do
= pluralize label.open_issues_count, 'open issue'
diff --git a/app/views/projects/merge_requests/_merge_request.html.haml b/app/views/projects/merge_requests/_merge_request.html.haml
index e25bf917b43..18cf3f14f0b 100644
--- a/app/views/projects/merge_requests/_merge_request.html.haml
+++ b/app/views/projects/merge_requests/_merge_request.html.haml
@@ -1,7 +1,7 @@
%li{ class: mr_css_classes(merge_request) }
.merge-request-title
%span.merge-request-title-text
- = link_to_gfm merge_request.title, merge_request_path(merge_request), class: "row_title"
+ = link_to_gfm merge_request.title, merge_request_path(merge_request), class: "title"
%ul.controls.light
- if merge_request.merged?
%li
@@ -24,6 +24,17 @@
%li
= link_to_member(merge_request.source_project, merge_request.assignee, name: false, title: "Assigned to :name")
+ - upvotes, downvotes = merge_request.upvotes, merge_request.downvotes
+ - if upvotes > 0
+ %li
+ = icon('thumbs-up')
+ = upvotes
+
+ - if downvotes > 0
+ %li
+ = icon('thumbs-down')
+ = downvotes
+
- note_count = merge_request.mr_and_commit_notes.user.count
- if note_count > 0
%li
@@ -37,7 +48,7 @@
= note_count
.merge-request-info
- \##{merge_request.iid} &middot;
+ #{merge_request.to_reference} &middot;
opened #{time_ago_with_tooltip(merge_request.created_at, placement: 'bottom')}
by #{link_to_member(@project, merge_request.author, avatar: false)}
- if merge_request.target_project.default_branch != merge_request.target_branch
diff --git a/app/views/projects/merge_requests/_new_compare.html.haml b/app/views/projects/merge_requests/_new_compare.html.haml
index 236a545c840..01dc7519bee 100644
--- a/app/views/projects/merge_requests/_new_compare.html.haml
+++ b/app/views/projects/merge_requests/_new_compare.html.haml
@@ -33,23 +33,18 @@
%div= msg
- elsif @merge_request.source_branch.present? && @merge_request.target_branch.present?
- - if @merge_request.compare_failed
- .alert.alert-danger
- %h4 Compare failed
- %p We can't compare selected branches. It may be because of huge diff. Please try again or select different branches.
- - else
- .light-well.append-bottom-default
- .center
- %h4
- There isn't anything to merge.
- %p.slead
- - if @merge_request.source_branch == @merge_request.target_branch
- You'll need to use different branch names to get a valid comparison.
- - else
- %span.label-branch #{@merge_request.source_branch}
- and
- %span.label-branch #{@merge_request.target_branch}
- are the same.
+ .light-well.append-bottom-default
+ .center
+ %h4
+ There isn't anything to merge.
+ %p.slead
+ - if @merge_request.source_branch == @merge_request.target_branch
+ You'll need to use different branch names to get a valid comparison.
+ - else
+ %span.label-branch #{@merge_request.source_branch}
+ and
+ %span.label-branch #{@merge_request.target_branch}
+ are the same.
.form-actions
diff --git a/app/views/projects/merge_requests/_new_submit.html.haml b/app/views/projects/merge_requests/_new_submit.html.haml
index 4c5a9818e3e..9e59f7df71b 100644
--- a/app/views/projects/merge_requests/_new_submit.html.haml
+++ b/app/views/projects/merge_requests/_new_submit.html.haml
@@ -31,22 +31,18 @@
%li.diffs-tab.active
= link_to url_for(params), data: {target: 'div#diffs', action: 'diffs', toggle: 'tab'} do
Changes
- %span.badge= @diffs.size
+ %span.badge= @diffs.real_size
.tab-content
#commits.commits.tab-pane
= render "projects/merge_requests/show/commits"
#diffs.diffs.tab-pane.active
- - if @diffs.present?
- = render "projects/diffs/diffs", diffs: @diffs, project: @project, diff_refs: @merge_request.diff_refs
- - elsif @commits.size > MergeRequestDiff::COMMITS_SAFE_SIZE
+ - if @commits.size > MergeRequestDiff::COMMITS_SAFE_SIZE
.alert.alert-danger
%h4 This comparison includes more than #{MergeRequestDiff::COMMITS_SAFE_SIZE} commits.
%p To preserve performance the line changes are not shown.
- else
- .alert.alert-danger
- %h4 This comparison includes a huge diff.
- %p To preserve performance the line changes are not shown.
+ = render "projects/diffs/diffs", diffs: @diffs, project: @project, diff_refs: @merge_request.diff_refs
- if @ci_commit
#builds.builds.tab-pane
= render "projects/merge_requests/show/builds"
diff --git a/app/views/projects/merge_requests/_show.html.haml b/app/views/projects/merge_requests/_show.html.haml
index da67645bc2b..b262892ac65 100644
--- a/app/views/projects/merge_requests/_show.html.haml
+++ b/app/views/projects/merge_requests/_show.html.haml
@@ -1,4 +1,4 @@
-- page_title "#{@merge_request.title} (##{@merge_request.iid})", "Merge Requests"
+- page_title "#{@merge_request.title} (#{@merge_request.to_reference})", "Merge Requests"
- page_description @merge_request.description
- page_card_attributes @merge_request.card_attributes
@@ -34,6 +34,8 @@
%span into
= link_to namespace_project_commits_path(@project.namespace, @project, @merge_request.target_branch), class: "label-branch" do
= @merge_request.target_branch
+ - if @merge_request.open? && @merge_request.diverged_from_target_branch?
+ %span (#{pluralize(@merge_request.diverged_commits_count, 'commit')} behind)
= render "projects/merge_requests/show/how_to_merge"
= render "projects/merge_requests/widget/show.html.haml"
@@ -62,7 +64,7 @@
%li.diffs-tab
= link_to diffs_namespace_project_merge_request_path(@project.namespace, @project, @merge_request), data: {target: 'div#diffs', action: 'diffs', toggle: 'tab'} do
Changes
- %span.badge= @merge_request.diffs.size
+ %span.badge= @merge_request.diff_size
.tab-content
#notes.notes.tab-pane.voting_notes
@@ -85,6 +87,8 @@
= spinner
= render 'shared/issuable/sidebar', issuable: @merge_request
+- if @merge_request.can_be_reverted?
+ = render "projects/commit/revert", commit: @merge_request.merge_commit, title: @merge_request.title
:javascript
var merge_request;
diff --git a/app/views/projects/merge_requests/show/_diffs.html.haml b/app/views/projects/merge_requests/show/_diffs.html.haml
index 64cd484193e..1b0bae86ad4 100644
--- a/app/views/projects/merge_requests/show/_diffs.html.haml
+++ b/app/views/projects/merge_requests/show/_diffs.html.haml
@@ -1,5 +1,5 @@
- if @merge_request_diff.collected?
- = render "projects/diffs/diffs", diffs: params[:w] == '1' ? @merge_request.diffs_no_whitespace : @merge_request.diffs,
+ = render "projects/diffs/diffs", diffs: @merge_request.diffs(diff_options),
project: @merge_request.project, diff_refs: @merge_request.diff_refs
- elsif @merge_request_diff.empty?
.nothing-here-block Nothing to merge from #{@merge_request.source_branch} into #{@merge_request.target_branch}
diff --git a/app/views/projects/merge_requests/show/_mr_title.html.haml b/app/views/projects/merge_requests/show/_mr_title.html.haml
index 473fbff721b..d24c12251f3 100644
--- a/app/views/projects/merge_requests/show/_mr_title.html.haml
+++ b/app/views/projects/merge_requests/show/_mr_title.html.haml
@@ -1,13 +1,25 @@
.detail-page-header
.status-box{ class: status_box_class(@merge_request) }
- = @merge_request.state_human_name
- %span.identifier
- Merge Request ##{@merge_request.iid}
- %span.creator
- &middot;
- opened by #{link_to_member(@project, @merge_request.author, size: 24)}
- &middot;
- = time_ago_with_tooltip(@merge_request.created_at)
+ %span.hidden-xs
+ = @merge_request.state_human_name
+ %span.hidden-sm.hidden-md.hidden-lg
+ = icon(@merge_request.state_icon_name)
+ %a.btn.btn-default.pull-right.hidden-sm.hidden-md.hidden-lg.gutter-toggle{ href: "#" }
+ = icon('angle-double-left')
+ .issue-meta
+ %strong.identifier
+ Merge Request ##{@merge_request.iid}
+ %span.creator
+ by
+ .editor-details
+ %strong
+ = link_to_member(@project, @merge_request.author, size: 24, mobile_classes: "hidden-xs")
+ %span.hidden-xs
+ = '@' + @merge_request.author.username
+ %strong
+ = link_to_member(@project, @merge_request.author, size: 24, mobile_classes: "hidden-sm hidden-md hidden-lg",
+ by_username: true, avatar: false)
+ = time_ago_with_tooltip(@merge_request.created_at)
.issue-btn-group.pull-right
- if can?(current_user, :update_merge_request, @merge_request)
diff --git a/app/views/projects/merge_requests/update.js.haml b/app/views/projects/merge_requests/update.js.haml
index ce5157d69a2..9cce5660e1c 100644
--- a/app/views/projects/merge_requests/update.js.haml
+++ b/app/views/projects/merge_requests/update.js.haml
@@ -1,3 +1,3 @@
-$('aside.right-sidebar')[0].outerHTML= "#{escape_javascript(render 'shared/issuable/sidebar', issuable: @merge_request)}";
-$('aside.right-sidebar').effect('highlight')
-merge_request = new MergeRequest();
+$('aside.right-sidebar')[0].outerHTML = "#{escape_javascript(render 'shared/issuable/sidebar', issuable: @merge_request)}";
+$('aside.right-sidebar').effect('highlight');
+new IssuableContext();
diff --git a/app/views/projects/merge_requests/widget/_merged.html.haml b/app/views/projects/merge_requests/widget/_merged.html.haml
index d1d602eecdc..3abae9f0bf6 100644
--- a/app/views/projects/merge_requests/widget/_merged.html.haml
+++ b/app/views/projects/merge_requests/widget/_merged.html.haml
@@ -8,20 +8,18 @@
#{time_ago_with_tooltip(@merge_request.merge_event.created_at)}
%div
- if !@merge_request.source_branch_exists? || (params[:delete_source] == 'true')
- The changes were merged into
- #{link_to @merge_request.target_branch, namespace_project_commits_path(@project.namespace, @project, @merge_request.target_branch), class: "label-branch"}.
- The source branch has been removed.
-
+ %p
+ The changes were merged into
+ #{link_to @merge_request.target_branch, namespace_project_commits_path(@project.namespace, @project, @merge_request.target_branch), class: "label-branch"}.
+ The source branch has been removed.
+ = render 'projects/merge_requests/widget/merged_buttons'
- elsif @merge_request.can_remove_source_branch?(current_user)
.remove_source_branch_widget
%p
The changes were merged into
#{link_to @merge_request.target_branch, namespace_project_commits_path(@project.namespace, @project, @merge_request.target_branch), class: "label-branch"}.
You can remove the source branch now.
- = link_to namespace_project_branch_path(@merge_request.source_project.namespace, @merge_request.source_project, @merge_request.source_branch), remote: true, method: :delete, class: "btn btn-primary btn-sm remove_source_branch" do
- %i.fa.fa-times
- Remove Source Branch
-
+ = render 'projects/merge_requests/widget/merged_buttons', source_branch_exists: true
.remove_source_branch_widget.failed.hide
%p
Failed to remove source branch '#{@merge_request.source_branch}'.
diff --git a/app/views/projects/merge_requests/widget/_merged_buttons.haml b/app/views/projects/merge_requests/widget/_merged_buttons.haml
new file mode 100644
index 00000000000..85a3a6ba9e2
--- /dev/null
+++ b/app/views/projects/merge_requests/widget/_merged_buttons.haml
@@ -0,0 +1,11 @@
+- source_branch_exists = local_assigns.fetch(:source_branch_exists, false)
+- mr_can_be_reverted = @merge_request.can_be_reverted?
+
+- if source_branch_exists || mr_can_be_reverted
+ .btn-group
+ - if source_branch_exists
+ = link_to namespace_project_branch_path(@merge_request.source_project.namespace, @merge_request.source_project, @merge_request.source_branch), remote: true, method: :delete, class: "btn btn-default btn-grouped btn-sm remove_source_branch" do
+ = icon('trash-o')
+ Remove Source Branch
+ - if mr_can_be_reverted
+ = revert_commit_link(@merge_request.merge_commit, namespace_project_merge_request_path(@project.namespace, @project, @merge_request), btn_class: 'sm')
diff --git a/app/views/projects/merge_requests/widget/open/_accept.html.haml b/app/views/projects/merge_requests/widget/open/_accept.html.haml
index d9a1730a8bc..807833741af 100644
--- a/app/views/projects/merge_requests/widget/open/_accept.html.haml
+++ b/app/views/projects/merge_requests/widget/open/_accept.html.haml
@@ -1,6 +1,6 @@
- status_class = @ci_commit ? " ci-#{@ci_commit.status}" : nil
-= form_for [:merge, @project.namespace.becomes(Namespace), @project, @merge_request], remote: true, method: :post, html: { class: 'accept-mr-form js-requires-input' } do |f|
+= form_for [:merge, @project.namespace.becomes(Namespace), @project, @merge_request], remote: true, method: :post, html: { class: 'accept-mr-form js-quick-submit js-requires-input' } do |f|
= hidden_field_tag :authenticity_token, form_authenticity_token
.accept-merge-holder.clearfix.js-toggle-container
.clearfix
diff --git a/app/views/projects/milestones/_form.html.haml b/app/views/projects/milestones/_form.html.haml
index 39aa2437e18..23f2bca7baf 100644
--- a/app/views/projects/milestones/_form.html.haml
+++ b/app/views/projects/milestones/_form.html.haml
@@ -1,4 +1,4 @@
-= form_for [@project.namespace.becomes(Namespace), @project, @milestone], html: {class: 'form-horizontal milestone-form gfm-form js-requires-input'} do |f|
+= form_for [@project.namespace.becomes(Namespace), @project, @milestone], html: {class: 'form-horizontal milestone-form gfm-form js-quick-submit js-requires-input'} do |f|
-if @milestone.errors.any?
.alert.alert-danger
%ul
@@ -9,12 +9,12 @@
.form-group
= f.label :title, "Title", class: "control-label"
.col-sm-10
- = f.text_field :title, maxlength: 255, class: "form-control js-quick-submit", required: true, autofocus: true
+ = f.text_field :title, maxlength: 255, class: "form-control", required: true, autofocus: true
.form-group.milestone-description
= f.label :description, "Description", class: "control-label"
.col-sm-10
= render layout: 'projects/md_preview', locals: { preview_class: "md-preview" } do
- = render 'projects/zen', f: f, attr: :description, classes: 'description form-control js-quick-submit'
+ = render 'projects/zen', f: f, attr: :description, classes: 'description form-control'
= render 'projects/notes/hints'
.clearfix
.error-alert
diff --git a/app/views/projects/milestones/_issue.html.haml b/app/views/projects/milestones/_issue.html.haml
deleted file mode 100644
index 133d802aaca..00000000000
--- a/app/views/projects/milestones/_issue.html.haml
+++ /dev/null
@@ -1,9 +0,0 @@
-%li{ id: dom_id(issue, 'sortable'), class: 'issue-row', 'data-iid' => issue.iid, 'data-url' => issue_path(issue) }
- .pull-right.assignee-icon
- - if issue.assignee
- = image_tag avatar_icon(issue.assignee, 16), class: "avatar s16", alt: ''
- %span
- = link_to [@project.namespace.becomes(Namespace), @project, issue] do
- %span.cgray ##{issue.iid}
- = link_to_gfm issue.title, [@project.namespace.becomes(Namespace), @project, issue], title: issue.title
-
diff --git a/app/views/projects/milestones/_issues.html.haml b/app/views/projects/milestones/_issues.html.haml
deleted file mode 100644
index 6e4df75a3df..00000000000
--- a/app/views/projects/milestones/_issues.html.haml
+++ /dev/null
@@ -1,6 +0,0 @@
-.panel.panel-default
- .panel-heading= title
- %ul{ class: "well-list issues-sortable-list", id: "issues-list-#{id}", "data-state" => id }
- - issues.sort_by(&:position).each do |issue|
- = render 'issue', issue: issue
- %li.light.ui-sort-disabled Drag and drop available
diff --git a/app/views/projects/milestones/_merge_request.html.haml b/app/views/projects/milestones/_merge_request.html.haml
deleted file mode 100644
index a1033607c5d..00000000000
--- a/app/views/projects/milestones/_merge_request.html.haml
+++ /dev/null
@@ -1,8 +0,0 @@
-%li{ id: dom_id(merge_request, 'sortable'), class: 'mr-row', 'data-iid' => merge_request.iid, 'data-url' => merge_request_path(merge_request) }
- %span.str-truncated
- = link_to [@project.namespace.becomes(Namespace), @project, merge_request] do
- %span.cgray ##{merge_request.iid}
- = link_to_gfm merge_request.title, [@project.namespace.becomes(Namespace), @project, merge_request], title: merge_request.title
- .pull-right.assignee-icon
- - if merge_request.assignee
- = image_tag avatar_icon(merge_request.assignee, 16), class: "avatar s16", alt: ''
diff --git a/app/views/projects/milestones/_merge_requests.html.haml b/app/views/projects/milestones/_merge_requests.html.haml
deleted file mode 100644
index 00889a5eb24..00000000000
--- a/app/views/projects/milestones/_merge_requests.html.haml
+++ /dev/null
@@ -1,6 +0,0 @@
-.panel.panel-default
- .panel-heading= title
- %ul{ class: "well-list merge_requests-sortable-list", id: "merge_requests-list-#{id}", "data-state" => id }
- - merge_requests.sort_by(&:position).each do |merge_request|
- = render 'merge_request', merge_request: merge_request
- %li.light.ui-sort-disabled Drag and drop available
diff --git a/app/views/projects/milestones/_milestone.html.haml b/app/views/projects/milestones/_milestone.html.haml
index 67d95ab0364..77b566db6b6 100644
--- a/app/views/projects/milestones/_milestone.html.haml
+++ b/app/views/projects/milestones/_milestone.html.haml
@@ -1,31 +1,5 @@
-%li{class: "milestone milestone-#{milestone.closed? ? 'closed' : 'open'}", id: dom_id(milestone) }
- .row
- .col-sm-6
- %strong
- = link_to_gfm truncate(milestone.title, length: 100), namespace_project_milestone_path(milestone.project.namespace, milestone.project, milestone)
-
- .col-sm-6
- .pull-right.light #{milestone.percent_complete}% complete
- .row
- .col-sm-6
- = link_to namespace_project_issues_path(milestone.project.namespace, milestone.project, milestone_title: milestone.title) do
- = pluralize milestone.issues.count, 'Issue'
- &middot;
- = link_to namespace_project_merge_requests_path(milestone.project.namespace, milestone.project, milestone_title: milestone.title) do
- = pluralize milestone.merge_requests.count, 'Merge Request'
- .col-sm-6
- = milestone_progress_bar(milestone)
-
- .row
- .col-sm-6
- = render 'shared/milestone_expired', milestone: milestone
- .col-sm-6
- - if can?(current_user, :admin_milestone, milestone.project) and milestone.active?
- = link_to edit_namespace_project_milestone_path(milestone.project.namespace, milestone.project, milestone), class: "btn btn-xs" do
- = icon('pencil-square-o')
- Edit
- \
- = link_to 'Close Milestone', namespace_project_milestone_path(@project.namespace, @project, milestone, milestone: {state_event: :close }), method: :put, remote: true, class: "btn btn-xs btn-close"
- = link_to namespace_project_milestone_path(milestone.project.namespace, milestone.project, milestone), data: { confirm: 'Are you sure?' }, method: :delete, class: "btn btn-xs btn-remove" do
- = icon('trash-o')
- Delete
+= render 'shared/milestones/milestone',
+ milestone_path: namespace_project_milestone_path(milestone.project.namespace, milestone.project, milestone),
+ issues_path: namespace_project_issues_path(milestone.project.namespace, milestone.project, milestone_title: milestone.title),
+ merge_requests_path: namespace_project_merge_requests_path(milestone.project.namespace, milestone.project, milestone_title: milestone.title),
+ milestone: milestone
diff --git a/app/views/projects/milestones/show.html.haml b/app/views/projects/milestones/show.html.haml
index 528a4f9552f..b4597043a27 100644
--- a/app/views/projects/milestones/show.html.haml
+++ b/app/views/projects/milestones/show.html.haml
@@ -24,7 +24,7 @@
- else
= link_to 'Reopen Milestone', namespace_project_milestone_path(@project.namespace, @project, @milestone, milestone: {state_event: :activate }), method: :put, class: "btn btn-reopen btn-nr btn-grouped"
- = link_to namespace_project_milestone_path(@project.namespace, @project, @milestone), data: { confirm: 'Are you sure?' }, method: :delete, class: "btn btn-grouped btn-nr btn-remove" do
+ = link_to namespace_project_milestone_path(@project.namespace, @project, @milestone), data: { confirm: 'Are you sure?' }, method: :delete, class: "btn btn-grouped btn-nr" do
= icon('trash-o')
Delete
@@ -32,7 +32,7 @@
= icon('pencil-square-o')
Edit
-.detail-page-description.content-block
+.detail-page-description.milestone-detail.second-block
%h2.title
= markdown escape_once(@milestone.title), pipeline: :single_line
%div
@@ -42,89 +42,9 @@
= preserve do
= markdown @milestone.description
-- if @milestone.issues.any? && @milestone.can_be_closed?
+- if @milestone.complete? && @milestone.active?
.alert.alert-success.prepend-top-default
%span All issues for this milestone are closed. You may close milestone now.
-.context.prepend-top-default
- %p.lead
- Progress:
- #{@milestone.closed_items_count} closed
- &ndash;
- #{@milestone.open_items_count} open
- &nbsp;
- %span.light #{@milestone.percent_complete}% complete
- %span.pull-right= @milestone.expires_at
- = milestone_progress_bar(@milestone)
-
-%ul.nav-links.no-top.no-bottom
- %li.active
- = link_to '#tab-issues', 'data-toggle' => 'tab' do
- Issues
- %span.badge= @issues.count
- %li
- = link_to '#tab-merge-requests', 'data-toggle' => 'tab' do
- Merge Requests
- %span.badge= @merge_requests.count
- %li
- = link_to '#tab-participants', 'data-toggle' => 'tab' do
- Participants
- %span.badge= @users.count
-
-.tab-content
- .tab-pane.active#tab-issues
- .content-block.oneline-block
- .controls
- - if can?(current_user, :create_issue, @project)
- = link_to new_namespace_project_issue_path(@project.namespace, @project, issue: { milestone_id: @milestone.id }), class: "btn btn-grouped", title: "New Issue" do
- %i.fa.fa-plus
- New Issue
- - if can?(current_user, :read_issue, @project)
- = link_to 'Browse Issues', namespace_project_issues_path(@milestone.project.namespace, @milestone.project, milestone_title: @milestone.title), class: "btn btn-grouped"
-
- .oneline
- All issues in this milestone
-
- .row.prepend-top-default
- .col-md-4
- = render('issues', title: 'Unstarted Issues (open and unassigned)', issues: @issues.opened.unassigned, id: 'unassigned')
- .col-md-4
- = render('issues', title: 'Ongoing Issues (open and assigned)', issues: @issues.opened.assigned, id: 'ongoing')
- .col-md-4
- = render('issues', title: 'Completed Issues (closed)', issues: @issues.closed, id: 'closed')
-
- .tab-pane#tab-merge-requests
- .content-block.oneline-block
- .controls
- - if can?(current_user, :read_merge_request, @project)
- = link_to 'Browse Merge Requests', namespace_project_merge_requests_path(@milestone.project.namespace, @milestone.project, milestone_title: @milestone.title), class: "btn btn-grouped"
-
- .oneline
- All merge requests in this milestone
-
- .row.prepend-top-default
- .col-md-3
- = render('merge_requests', title: 'Work in progress (open and unassigned)', merge_requests: @merge_requests.opened.unassigned, id: 'unassigned')
- .col-md-3
- = render('merge_requests', title: 'Waiting for merge (open and assigned)', merge_requests: @merge_requests.opened.assigned, id: 'ongoing')
- .col-md-3
- = render('merge_requests', title: 'Rejected (closed)', merge_requests: @merge_requests.closed, id: 'closed')
- .col-md-3
- .panel.panel-primary
- .panel-heading Merged
- %ul.well-list
- - @merge_requests.merged.each do |merge_request|
- = render 'merge_request', merge_request: merge_request
-
- .tab-pane#tab-participants
- .content-block.oneline-block
- All participants to this milestone
-
- %ul.bordered-list
- - @users.each do |user|
- %li
- = link_to user, title: user.name, class: "darken" do
- = image_tag avatar_icon(user, 32), class: "avatar s32"
- %strong= truncate(user.name, lenght: 40)
- %br
- %small.cgray= user.username
+= render 'shared/milestones/summary', milestone: @milestone, project: @project
+= render 'shared/milestones/tabs', milestone: @milestone
diff --git a/app/views/projects/notes/_edit_form.html.haml b/app/views/projects/notes/_edit_form.html.haml
index 5d78652befa..b5f076088c7 100644
--- a/app/views/projects/notes/_edit_form.html.haml
+++ b/app/views/projects/notes/_edit_form.html.haml
@@ -1,8 +1,8 @@
.note-edit-form
- = form_for note, url: namespace_project_note_path(@project.namespace, @project, note), method: :put, remote: true, authenticity_token: true do |f|
+ = form_for note, url: namespace_project_note_path(@project.namespace, @project, note), method: :put, remote: true, authenticity_token: true, class: 'js-quick-submit' do |f|
= note_target_fields(note)
= render layout: 'projects/md_preview', locals: { preview_class: 'md-preview' } do
- = render 'projects/zen', f: f, attr: :note, classes: 'note_text js-note-text js-task-list-field js-quick-submit'
+ = render 'projects/zen', f: f, attr: :note, classes: 'note_text js-note-text js-task-list-field'
= render 'projects/notes/hints'
.note-form-actions
diff --git a/app/views/projects/notes/_form.html.haml b/app/views/projects/notes/_form.html.haml
index f10a4145d62..09740d8ea12 100644
--- a/app/views/projects/notes/_form.html.haml
+++ b/app/views/projects/notes/_form.html.haml
@@ -1,4 +1,4 @@
-= form_for [@project.namespace.becomes(Namespace), @project, @note], remote: true, html: { :'data-type' => 'json', multipart: true, id: nil, class: "new_note js-new-note-form common-note-form gfm-form" }, authenticity_token: true do |f|
+= form_for [@project.namespace.becomes(Namespace), @project, @note], remote: true, html: { :'data-type' => 'json', multipart: true, id: nil, class: "new_note js-new-note-form js-quick-submit common-note-form gfm-form" }, authenticity_token: true do |f|
= hidden_field_tag :view, diff_view
= hidden_field_tag :line_type
= note_target_fields(@note)
@@ -8,7 +8,7 @@
= f.hidden_field :noteable_type
= render layout: 'projects/md_preview', locals: { preview_class: "md-preview", referenced_users: true } do
- = render 'projects/zen', f: f, attr: :note, classes: 'note_text js-note-text js-quick-submit'
+ = render 'projects/zen', f: f, attr: :note, classes: 'note_text js-note-text'
= render 'projects/notes/hints'
.error-alert
diff --git a/app/views/projects/notes/_note.html.haml b/app/views/projects/notes/_note.html.haml
index e858c412836..52972576aff 100644
--- a/app/views/projects/notes/_note.html.haml
+++ b/app/views/projects/notes/_note.html.haml
@@ -28,7 +28,7 @@
%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')
- if note.updated_at != note.created_at
- %span
+ %span.note-updated-at
&middot;
= icon('edit', title: 'edited')
= time_ago_with_tooltip(note.updated_at, placement: 'bottom', html_class: 'note_edited_ago')
diff --git a/app/views/projects/refs/logs_tree.js.haml b/app/views/projects/refs/logs_tree.js.haml
index db7f244d002..8ee2aef0e61 100644
--- a/app/views/projects/refs/logs_tree.js.haml
+++ b/app/views/projects/refs/logs_tree.js.haml
@@ -8,12 +8,9 @@
row.find("td.tree_time_ago").html('#{escape_javascript time_ago_with_tooltip(commit.committed_date)}');
row.find("td.tree_commit").html('#{escape_javascript render("projects/tree/tree_commit_column", commit: commit)}');
-- if @logs.present?
+- if @more_log_url
:plain
- var current_url = location.href.replace(/\/?$/, '/');
- var log_url = "#{escape_javascript(@log_url)}".replace(/\/?$/, '/');
-
- if(current_url == log_url) {
+ if($('#tree-slider').length) {
// Load more commit logs for each file in tree
// if we still on the same page
var url = "#{escape_javascript(@more_log_url)}";
diff --git a/app/views/projects/releases/edit.html.haml b/app/views/projects/releases/edit.html.haml
index bc80f2f29ad..c4a3f06ee06 100644
--- a/app/views/projects/releases/edit.html.haml
+++ b/app/views/projects/releases/edit.html.haml
@@ -9,9 +9,9 @@
%strong #{@tag.name}
.prepend-top-default
- = form_for(@release, method: :put, url: namespace_project_tag_release_path(@project.namespace, @project, @tag.name), html: { class: 'form-horizontal gfm-form release-form' }) do |f|
+ = form_for(@release, method: :put, url: namespace_project_tag_release_path(@project.namespace, @project, @tag.name), html: { class: 'form-horizontal gfm-form release-form js-quick-submit' }) do |f|
= render layout: 'projects/md_preview', locals: { preview_class: "md-preview", referenced_users: true } do
- = render 'projects/zen', f: f, attr: :description, classes: 'description js-quick-submit form-control'
+ = render 'projects/zen', f: f, attr: :description, classes: 'description form-control'
= render 'projects/notes/hints'
.error-alert
.form-actions.prepend-top-default
diff --git a/app/views/projects/tags/destroy.js.haml b/app/views/projects/tags/destroy.js.haml
new file mode 100644
index 00000000000..ffeacb5a004
--- /dev/null
+++ b/app/views/projects/tags/destroy.js.haml
@@ -0,0 +1,3 @@
+$('.js-totaltags-count').html("#{@repository.tags.size}");
+- if @repository.tags.empty?
+ $('.tags').load(document.URL + ' .nothing-here-block').hide().fadeIn(1000)
diff --git a/app/views/projects/tags/new.html.haml b/app/views/projects/tags/new.html.haml
index 3a2f75fecaa..77c7c4d23de 100644
--- a/app/views/projects/tags/new.html.haml
+++ b/app/views/projects/tags/new.html.haml
@@ -10,7 +10,7 @@
New Tag
%hr
-= form_tag namespace_project_tags_path, method: :post, id: "new-tag-form", class: "form-horizontal gfm-form tag-form js-requires-input" do
+= form_tag namespace_project_tags_path, method: :post, id: "new-tag-form", class: "form-horizontal gfm-form tag-form js-quick-submit js-requires-input" do
.form-group
= label_tag :tag_name, nil, class: 'control-label'
.col-sm-10
@@ -30,7 +30,7 @@
= label_tag :release_description, 'Release notes', class: 'control-label'
.col-sm-10
= render layout: 'projects/md_preview', locals: { preview_class: "md-preview", referenced_users: true } do
- = render 'projects/zen', attr: :release_description, classes: 'description js-quick-submit form-control'
+ = render 'projects/zen', attr: :release_description, classes: 'description form-control'
= render 'projects/notes/hints'
.help-block Optionally, add release notes to the tag. They will be stored in the GitLab database and displayed on the tags page.
.form-actions
diff --git a/app/views/projects/wikis/_form.html.haml b/app/views/projects/wikis/_form.html.haml
index 1d257818dcd..f0d1932e23c 100644
--- a/app/views/projects/wikis/_form.html.haml
+++ b/app/views/projects/wikis/_form.html.haml
@@ -1,4 +1,4 @@
-= form_for [@project.namespace.becomes(Namespace), @project, @page], method: @page.persisted? ? :put : :post, html: { class: 'form-horizontal wiki-form gfm-form prepend-top-default' } do |f|
+= form_for [@project.namespace.becomes(Namespace), @project, @page], method: @page.persisted? ? :put : :post, html: { class: 'form-horizontal wiki-form gfm-form prepend-top-default js-quick-submit' } do |f|
-if @page.errors.any?
#error_explanation
.alert.alert-danger
@@ -15,7 +15,7 @@
= f.label :content, class: 'control-label'
.col-sm-10
= render layout: 'projects/md_preview', locals: { preview_class: "md-preview" } do
- = render 'projects/zen', f: f, attr: :content, classes: 'description form-control js-quick-submit'
+ = render 'projects/zen', f: f, attr: :content, classes: 'description form-control'
= render 'projects/notes/hints'
.clearfix
diff --git a/app/views/projects/wikis/_main_links.html.haml b/app/views/projects/wikis/_main_links.html.haml
index 29bf5d62abe..2b91b7e8f65 100644
--- a/app/views/projects/wikis/_main_links.html.haml
+++ b/app/views/projects/wikis/_main_links.html.haml
@@ -1,12 +1,11 @@
-%span.pull-right
- - if (@page && @page.persisted?)
- = link_to namespace_project_wiki_history_path(@project.namespace, @project, @page), class: "btn btn-grouped" do
- Page History
- - if can?(current_user, :create_wiki, @project)
- = link_to namespace_project_wiki_edit_path(@project.namespace, @project, @page), class: "btn btn-grouped" do
- %i.fa.fa-pencil-square-o
- Edit
- - if can?(current_user, :admin_wiki, @project)
- = link_to namespace_project_wiki_path(@project.namespace, @project, @page), data: { confirm: "Are you sure you want to delete this page?"}, method: :delete, class: "btn btn-remove" do
- = icon('trash')
- Delete
+- if (@page && @page.persisted?)
+ = link_to namespace_project_wiki_history_path(@project.namespace, @project, @page), class: "btn btn-grouped" do
+ Page History
+ - if can?(current_user, :create_wiki, @project)
+ = link_to namespace_project_wiki_edit_path(@project.namespace, @project, @page), class: "btn btn-grouped" do
+ %i.fa.fa-pencil-square-o
+ Edit
+ - if can?(current_user, :admin_wiki, @project)
+ = link_to namespace_project_wiki_path(@project.namespace, @project, @page), data: { confirm: "Are you sure you want to delete this page?"}, method: :delete, class: "btn btn-remove" do
+ = icon('trash')
+ Delete
diff --git a/app/views/projects/wikis/_nav.html.haml b/app/views/projects/wikis/_nav.html.haml
index 56a53ffff2a..a722fbc5352 100644
--- a/app/views/projects/wikis/_nav.html.haml
+++ b/app/views/projects/wikis/_nav.html.haml
@@ -16,4 +16,4 @@
= icon('plus')
New Page
- = render 'projects/wikis/new'
+= render 'projects/wikis/new'
diff --git a/app/views/projects/wikis/_new.html.haml b/app/views/projects/wikis/_new.html.haml
index 53b37b1104e..919daf0a7b2 100644
--- a/app/views/projects/wikis/_new.html.haml
+++ b/app/views/projects/wikis/_new.html.haml
@@ -5,9 +5,10 @@
%a.close{href: "#", "data-dismiss" => "modal"} ×
%h3.page-title New Wiki Page
.modal-body
- .form-group
- = label_tag :new_wiki_path do
- %span Page slug
- = text_field_tag :new_wiki_path, nil, placeholder: 'how-to-setup', class: 'form-control', required: true, :'data-wikis-path' => namespace_project_wikis_path(@project.namespace, @project)
- .form-actions
- = link_to 'Create Page', '#', class: 'build-new-wiki btn btn-create'
+ %form.new-wiki-page
+ .form-group
+ = label_tag :new_wiki_path do
+ %span Page slug
+ = text_field_tag :new_wiki_path, nil, placeholder: 'how-to-setup', class: 'form-control', required: true, :'data-wikis-path' => namespace_project_wikis_path(@project.namespace, @project), autofocus: true
+ .form-actions
+ = button_tag 'Create Page', class: 'build-new-wiki btn btn-create'
diff --git a/app/views/projects/wikis/edit.html.haml b/app/views/projects/wikis/edit.html.haml
index 23f64fbbd10..4dd818c7f67 100644
--- a/app/views/projects/wikis/edit.html.haml
+++ b/app/views/projects/wikis/edit.html.haml
@@ -1,16 +1,20 @@
- page_title "Edit", @page.title.capitalize, "Wiki"
= render "header_title"
-
= render 'nav'
-.gray-content-block
- .pull-right
+
+.top-area
+ .nav-text
+ %strong
+ - if @page.persisted?
+ = link_to @page.title.capitalize, namespace_project_wiki_path(@project.namespace, @project, @page)
+ - else
+ = @page.title.capitalize
+ %span.light
+ &middot;
+ Edit Page
+
+ .nav-controls
= render 'main_links'
- %h3.page-title.oneline
- %span.light Edit Page
- - if @page.persisted?
- = link_to @page.title, namespace_project_wiki_path(@project.namespace, @project, @page)
- - else
- = @page.title
= render 'form'
diff --git a/app/views/projects/wikis/history.html.haml b/app/views/projects/wikis/history.html.haml
index 4322146ce34..dcaddae2b04 100644
--- a/app/views/projects/wikis/history.html.haml
+++ b/app/views/projects/wikis/history.html.haml
@@ -1,11 +1,14 @@
- page_title "History", @page.title.capitalize, "Wiki"
= render "header_title"
-
= render 'nav'
-.gray-content-block
- %h3.page-title
- %span.light History for
- = link_to @page.title, namespace_project_wiki_path(@project.namespace, @project, @page)
+
+.top-area
+ .nav-text
+ %strong
+ = link_to @page.title.capitalize, namespace_project_wiki_path(@project.namespace, @project, @page)
+ %span.light
+ &middot;
+ History
.table-holder
%table.table
diff --git a/app/views/projects/wikis/pages.html.haml b/app/views/projects/wikis/pages.html.haml
index aae1ad69ad9..92b494a513c 100644
--- a/app/views/projects/wikis/pages.html.haml
+++ b/app/views/projects/wikis/pages.html.haml
@@ -2,15 +2,12 @@
= render "header_title"
= render 'nav'
-.gray-content-block
- All pages in this wiki are listed below.
-
+
%ul.content-list
- @wiki_pages.each do |wiki_page|
%li
- %h4
- = link_to wiki_page.title, namespace_project_wiki_path(@project.namespace, @project, wiki_page)
- %small (#{wiki_page.format})
- .pull-right
- %small Last edited #{time_ago_with_tooltip(wiki_page.commit.authored_date)}
+ = link_to wiki_page.title, namespace_project_wiki_path(@project.namespace, @project, wiki_page)
+ %small (#{wiki_page.format})
+ .pull-right
+ %small Last edited #{time_ago_with_tooltip(wiki_page.commit.authored_date)}
= paginate @wiki_pages, theme: 'gitlab'
diff --git a/app/views/projects/wikis/show.html.haml b/app/views/projects/wikis/show.html.haml
index 309d40f52bc..067fb7f8f54 100644
--- a/app/views/projects/wikis/show.html.haml
+++ b/app/views/projects/wikis/show.html.haml
@@ -1,17 +1,18 @@
- page_title @page.title.capitalize, "Wiki"
= render "header_title"
-
= render 'nav'
-.gray-content-block
- = render 'main_links'
- %h3.page-title.oneline
- = @page.title.capitalize
+.top-area
+ .nav-text
+ %strong= @page.title.capitalize
%span.wiki-last-edit-by
&middot;
last edited by #{@page.commit.author.name} #{time_ago_with_tooltip(@page.commit.authored_date)}
+ .nav-controls
+ = render 'main_links'
+
- if @page.historical?
.warning_message
This is an old version of this page.
diff --git a/app/views/search/_form.html.haml b/app/views/search/_form.html.haml
index 17b0981f073..a9dbc84da29 100644
--- a/app/views/search/_form.html.haml
+++ b/app/views/search/_form.html.haml
@@ -11,4 +11,4 @@
= button_tag 'Search', class: "btn btn-primary"
- unless params[:snippets].eql? 'true'
%br
- = render 'filter'
+ = render 'filter' if current_user
diff --git a/app/views/search/_results.html.haml b/app/views/search/_results.html.haml
index d0e64537621..60df348891c 100644
--- a/app/views/search/_results.html.haml
+++ b/app/views/search/_results.html.haml
@@ -18,6 +18,8 @@
= render 'shared/projects/list', projects: @objects
- else
= render partial: "search/results/#{@scope.singularize}", collection: @objects
+
+ - if @scope != 'projects'
= paginate @objects, theme: 'gitlab'
:javascript
diff --git a/app/views/search/results/_snippet_blob.html.haml b/app/views/search/results/_snippet_blob.html.haml
index 6b77d24f50c..c9b7bd154af 100644
--- a/app/views/search/results/_snippet_blob.html.haml
+++ b/app/views/search/results/_snippet_blob.html.haml
@@ -30,7 +30,7 @@
.line-numbers
- snippet_chunks.each do |chunk|
- unless chunk[:data].empty?
- - chunk[:data].lines.to_a.size.times do |index|
+ - Gitlab::Git::Util.count_lines(chunk[:data]).times do |index|
- offset = defined?(chunk[:start_line]) ? chunk[:start_line] : 1
- i = index + offset
= link_to snippet_path+"#L#{i}", id: "L#{i}", rel: "#L#{i}", class: "diff-line-num" do
diff --git a/app/views/shared/_commit_message_container.html.haml b/app/views/shared/_commit_message_container.html.haml
index 7c57924277e..7afbaeddee8 100644
--- a/app/views/shared/_commit_message_container.html.haml
+++ b/app/views/shared/_commit_message_container.html.haml
@@ -7,7 +7,7 @@
.max-width-marker
= text_area_tag 'commit_message',
(params[:commit_message] || local_assigns[:text]),
- class: 'form-control js-commit-message js-quick-submit', placeholder: local_assigns[:placeholder],
+ class: 'form-control js-commit-message', placeholder: local_assigns[:placeholder],
required: true, rows: (local_assigns[:rows] || 3),
id: "commit_message-#{nonce}"
- if local_assigns[:hint]
diff --git a/app/views/shared/_issues.html.haml b/app/views/shared/_issues.html.haml
index 4b4c9e9eabe..8ff9d4c1c7f 100644
--- a/app/views/shared/_issues.html.haml
+++ b/app/views/shared/_issues.html.haml
@@ -8,7 +8,7 @@
.pull-right
= link_to 'New issue', new_namespace_project_issue_path(project.namespace, project)
- %ul.well-list.issues-list
+ %ul.content-list.issues-list
- group[1].each do |issue|
= render 'projects/issues/issue', issue: issue
= paginate @issues, theme: "gitlab"
diff --git a/app/views/shared/_label_row.html.haml b/app/views/shared/_label_row.html.haml
new file mode 100644
index 00000000000..8134b15d245
--- /dev/null
+++ b/app/views/shared/_label_row.html.haml
@@ -0,0 +1,4 @@
+%span.label-row
+ = link_to_label(label)
+ %span.prepend-left-10
+ = markdown(label.description, pipeline: :single_line)
diff --git a/app/views/shared/_merge_requests.html.haml b/app/views/shared/_merge_requests.html.haml
index be17a511b26..e74fc36c797 100644
--- a/app/views/shared/_merge_requests.html.haml
+++ b/app/views/shared/_merge_requests.html.haml
@@ -8,7 +8,7 @@
.pull-right
= link_to 'New merge request', new_namespace_project_merge_request_path(project.namespace, project)
- %ul.well-list.mr-list
+ %ul.content-list.mr-list
- group[1].each do |merge_request|
= render 'projects/merge_requests/merge_request', merge_request: merge_request
= paginate @merge_requests, theme: "gitlab"
diff --git a/app/views/shared/_no_ssh.html.haml b/app/views/shared/_no_ssh.html.haml
index 089179e677a..bb5fff2d3bb 100644
--- a/app/views/shared/_no_ssh.html.haml
+++ b/app/views/shared/_no_ssh.html.haml
@@ -1,6 +1,6 @@
- if cookies[:hide_no_ssh_message].blank? && !current_user.hide_no_ssh_key && current_user.require_ssh_key?
.no-ssh-key-message.alert.alert-warning.hidden-xs
- You won't be able to pull or push project code via SSH until you #{link_to 'add an SSH key', new_profile_key_path, class: 'alert-link'} to your profile
+ You won't be able to pull or push project code via SSH until you #{link_to 'add an SSH key', profile_keys_path, class: 'alert-link'} to your profile
.pull-right
= link_to "Don't show again", profile_path(user: {hide_no_ssh_key: true}), method: :put, class: 'alert-link'
diff --git a/app/views/shared/_project_limit.html.haml b/app/views/shared/_project_limit.html.haml
index 960ff00b49d..f4eb8e491b9 100644
--- a/app/views/shared/_project_limit.html.haml
+++ b/app/views/shared/_project_limit.html.haml
@@ -1,4 +1,4 @@
-- if cookies[:hide_project_limit_message].blank? && !current_user.hide_project_limit && !current_user.can_create_project?
+- if cookies[:hide_project_limit_message].blank? && !current_user.hide_project_limit && !current_user.can_create_project? && current_user.projects_limit > 0
.project-limit-message.alert.alert-warning.hidden-xs
You won't be able to create new projects because you have reached your project limit.
diff --git a/app/views/shared/_sort_dropdown.html.haml b/app/views/shared/_sort_dropdown.html.haml
index f09ab25276d..e3a6a5a68b6 100644
--- a/app/views/shared/_sort_dropdown.html.haml
+++ b/app/views/shared/_sort_dropdown.html.haml
@@ -20,3 +20,7 @@
= sort_title_milestone_soon
= link_to page_filter_path(sort: sort_value_milestone_later) do
= sort_title_milestone_later
+ = link_to page_filter_path(sort: sort_value_upvotes) do
+ = sort_title_upvotes
+ = link_to page_filter_path(sort: sort_value_downvotes) do
+ = sort_title_downvotes
diff --git a/app/views/shared/groups/_group.html.haml b/app/views/shared/groups/_group.html.haml
index 289b0bfe1eb..fb9a8db0889 100644
--- a/app/views/shared/groups/_group.html.haml
+++ b/app/views/shared/groups/_group.html.haml
@@ -22,13 +22,13 @@
= number_with_delimiter(group.users.count)
= image_tag group_icon(group), class: "avatar s40 hidden-xs"
- = link_to group, class: 'group-name' do
- %span.item-title= group.name
+ = link_to group, class: 'group-name title' do
+ = group.name
- if group_member
as
%span #{group_member.human_access}
- if group.description.present?
- .light
+ .description
= markdown(group.description, pipeline: :description)
diff --git a/app/views/shared/groups/_list.html.haml b/app/views/shared/groups/_list.html.haml
new file mode 100644
index 00000000000..1aa7ed1f2eb
--- /dev/null
+++ b/app/views/shared/groups/_list.html.haml
@@ -0,0 +1,6 @@
+- if groups.any?
+ %ul.content-list
+ - groups.each_with_index do |group, i|
+ = render "shared/groups/group", group: group
+- else
+ %h3 No groups found
diff --git a/app/views/shared/issuable/_filter.html.haml b/app/views/shared/issuable/_filter.html.haml
index b7e350d27af..e55159d996b 100644
--- a/app/views/shared/issuable/_filter.html.haml
+++ b/app/views/shared/issuable/_filter.html.haml
@@ -41,6 +41,10 @@
.filter-item.inline
= button_tag "Update issues", class: "btn update_selected_issues btn-save"
+- if @label
+ .gray-content-block.second-block
+ = render "shared/label_row", label: @label
+
:javascript
new UsersSelect();
$('form.filter-form').on('submit', function (event) {
diff --git a/app/views/shared/issuable/_form.html.haml b/app/views/shared/issuable/_form.html.haml
index 90dc0062481..d5a4aad05d9 100644
--- a/app/views/shared/issuable/_form.html.haml
+++ b/app/views/shared/issuable/_form.html.haml
@@ -9,7 +9,7 @@
= f.label :title, class: 'control-label'
.col-sm-10
= f.text_field :title, maxlength: 255, autofocus: true, autocomplete: 'off',
- class: 'form-control pad js-gfm-input js-quick-submit', required: true
+ class: 'form-control pad js-gfm-input', required: true
- if issuable.is_a?(MergeRequest)
%p.help-block
@@ -25,7 +25,7 @@
= render layout: 'projects/md_preview', locals: { preview_class: "md-preview", referenced_users: true } do
= render 'projects/zen', f: f, attr: :description,
- classes: 'description form-control js-quick-submit'
+ classes: 'description form-control'
= render 'projects/notes/hints'
.clearfix
.error-alert
diff --git a/app/views/shared/issuable/_participants.html.haml b/app/views/shared/issuable/_participants.html.haml
index ea61935487c..f1d92ef48b2 100644
--- a/app/views/shared/issuable/_participants.html.haml
+++ b/app/views/shared/issuable/_participants.html.haml
@@ -3,7 +3,8 @@
= icon('users')
%span
= participants.count
- .title
+ .title.hide-collapsed
= pluralize participants.count, "participant"
- participants.each do |participant|
- = link_to_member(@project, participant, name: false, size: 24)
+ %span.hide-collapsed
+ = link_to_member(@project, participant, name: false, size: 24)
diff --git a/app/views/shared/issuable/_sidebar.html.haml b/app/views/shared/issuable/_sidebar.html.haml
index ae96a45453f..36f06377886 100644
--- a/app/views/shared/issuable/_sidebar.html.haml
+++ b/app/views/shared/issuable/_sidebar.html.haml
@@ -1,24 +1,21 @@
%aside.right-sidebar{ class: sidebar_gutter_collapsed_class }
.issuable-sidebar
.block
- %span.issuable-count.pull-left
+ %span.issuable-count.hide-collapsed.pull-left
= issuable.iid
of
- = issuable_count(:all, @project)
+ = issuables_count(issuable)
%span.pull-right
%a.gutter-toggle{href: '#'}
- - if sidebar_gutter_collapsed?
- = icon('angle-double-left')
- - else
- = icon('angle-double-right')
- .issuable-nav.pull-right.btn-group{role: 'group', "aria-label" => '...'}
- - if has_prev_issuable?(@project, issuable.id)
- = link_to 'Prev', issuable_link_prev(@project, issuable), class: 'btn btn-default prev-btn'
+ = sidebar_gutter_toggle_icon
+ .issuable-nav.hide-collapsed.pull-right.btn-group{role: 'group', "aria-label" => '...'}
+ - if prev_issuable = prev_issuable_for(issuable)
+ = link_to 'Prev', [@project.namespace.becomes(Namespace), @project, prev_issuable], class: 'btn btn-default prev-btn'
- else
%a.btn.btn-default.disabled{href: '#'}
Prev
- - if has_next_issuable?(@project, issuable.id)
- = link_to 'Next', issuable_link_next(@project, issuable), class: 'btn btn-default next-btn'
+ - if next_issuable = next_issuable_for(issuable)
+ = link_to 'Next', [@project.namespace.becomes(Namespace), @project, next_issuable], class: 'btn btn-default next-btn'
- else
%a.btn.btn-default.disabled{href: '#'}
Next
@@ -30,13 +27,13 @@
= link_to_member_avatar(issuable.assignee, size: 24)
- else
= icon('user')
- .title
+ .title.hide-collapsed
%label
Assignee
- if can?(current_user, :"admin_#{issuable.to_ability_name}", @project)
.pull-right
= link_to 'Edit', '#', class: 'edit-link'
- .value
+ .value.hide-collapsed
- if issuable.assignee
%strong= link_to_member(@project, issuable.assignee, size: 24)
- if issuable.instance_of?(MergeRequest) && !issuable.can_be_merged_by?(issuable.assignee)
@@ -45,24 +42,24 @@
- else
.light None
- .selectbox
+ .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)
.block.milestone
.sidebar-collapsed-icon
- = icon('balance-scale')
+ = icon('clock-o')
%span
- if issuable.milestone
= issuable.milestone.title
- else
No
- .title
+ .title.hide-collapsed
%label
Milestone
- if can?(current_user, :"admin_#{issuable.to_ability_name}", @project)
.pull-right
= link_to 'Edit', '#', class: 'edit-link'
- .value
+ .value.hide-collapsed
- if issuable.milestone
%span.back-to-milestone
= link_to namespace_project_milestone_path(@project.namespace, @project, issuable.milestone) do
@@ -71,7 +68,7 @@
= issuable.milestone.title
- else
.light None
- .selectbox
+ .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'
@@ -82,18 +79,18 @@
= icon('tags')
%span
= issuable.labels.count
- .title
+ .title.hide-collapsed
%label Labels
- if can?(current_user, :"admin_#{issuable.to_ability_name}", @project)
.pull-right
= link_to 'Edit', '#', class: 'edit-link'
- .value.issuable-show-labels
+ .value.issuable-show-labels.hide-collapsed
- if issuable.labels.any?
- issuable.labels.each do |label|
= link_to_label(label, type: issuable.to_ability_name)
- else
.light None
- .selectbox
+ .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" }
@@ -104,12 +101,12 @@
.block.light
.sidebar-collapsed-icon
= icon('rss')
- .title
+ .title.hide-collapsed
%label.light Notifications
- subscribtion_status = subscribed ? 'subscribed' : 'unsubscribed'
- %button.btn.btn-block.btn-gray.subscribe-button{:type => 'button'}
+ %button.btn.btn-block.btn-gray.subscribe-button.hide-collapsed{:type => 'button'}
%span= subscribed ? 'Unsubscribe' : 'Subscribe'
- .subscription-status{data: {status: subscribtion_status}}
+ .subscription-status.hide-collapsed{data: {status: subscribtion_status}}
.unsubscribed{class: ( 'hidden' if subscribed )}
You're not receiving notifications from this thread.
.subscribed{class: ( 'hidden' unless subscribed )}
@@ -118,9 +115,8 @@
- project_ref = cross_project_reference(@project, issuable)
.block.project-reference
.sidebar-collapsed-icon
- = icon('clipboard')
- .title
- .cross-project-reference
+ = clipboard_button(clipboard_text: project_ref)
+ .cross-project-reference.hide-collapsed
%span
Reference:
%cite{title: project_ref}
diff --git a/app/views/shared/milestones/_issuable.html.haml b/app/views/shared/milestones/_issuable.html.haml
new file mode 100644
index 00000000000..f7c6fc14adf
--- /dev/null
+++ b/app/views/shared/milestones/_issuable.html.haml
@@ -0,0 +1,25 @@
+-# @project is present when viewing Project's milestone
+- project = @project || issuable.project
+- assignee = issuable.assignee
+- issuable_type = issuable.class.table_name
+- base_url_args = [project.namespace.becomes(Namespace), project, issuable_type]
+
+%li{ id: dom_id(issuable, 'sortable'), class: "issuable-row", 'data-iid' => issuable.iid, 'data-url' => polymorphic_path(issuable) }
+ %span
+ - if show_project_name
+ %strong #{project.name} &middot;
+ - elsif show_full_project_name
+ %strong #{project.name_with_namespace} &middot;
+ = link_to_gfm issuable.title, [project.namespace.becomes(Namespace), project, issuable], title: issuable.title
+ %div{class: 'issuable-detail'}
+ = link_to [project.namespace.becomes(Namespace), project, issuable] do
+ %span{ class: 'issuable-number' }>= issuable.to_reference
+
+ - issuable.labels.each do |label|
+ = link_to polymorphic_path(base_url_args, { milestone_title: @milestone.title, label_name: label.title, state: 'all' }) do
+ - render_colored_label(label)
+
+ - if assignee
+ = link_to polymorphic_path(base_url_args, { milestone_title: @milestone.title, assignee_id: issuable.assignee_id, state: 'all' }),
+ class: 'has_tooltip', data: { 'original-title' => "Assigned to #{sanitize(assignee.name)}", container: 'body' } do
+ - image_tag(avatar_icon(issuable.assignee, 16), class: "avatar s16", alt: '')
diff --git a/app/views/shared/milestones/_issuables.html.haml b/app/views/shared/milestones/_issuables.html.haml
new file mode 100644
index 00000000000..8619939dde7
--- /dev/null
+++ b/app/views/shared/milestones/_issuables.html.haml
@@ -0,0 +1,16 @@
+- show_counter = local_assigns.fetch(:show_counter, false)
+- primary = local_assigns.fetch(:primary, false)
+- panel_class = primary ? 'panel-primary' : 'panel-default'
+
+.panel{ class: panel_class }
+ .panel-heading
+ = title
+ - if show_counter
+ .pull-right= issuables.size
+
+ - class_prefix = dom_class(issuables).pluralize
+ %ul{ class: "well-list #{class_prefix}-sortable-list", id: "#{class_prefix}-list-#{id}", "data-state" => id }
+ = render partial: 'shared/milestones/issuable',
+ collection: issuables.sort_by(&:position),
+ as: :issuable,
+ locals: { show_project_name: show_project_name, show_full_project_name: show_full_project_name }
diff --git a/app/views/shared/milestones/_issues_tab.html.haml b/app/views/shared/milestones/_issues_tab.html.haml
new file mode 100644
index 00000000000..a8db7f8a556
--- /dev/null
+++ b/app/views/shared/milestones/_issues_tab.html.haml
@@ -0,0 +1,10 @@
+- args = { show_project_name: local_assigns.fetch(:show_project_name, false),
+ show_full_project_name: local_assigns.fetch(:show_full_project_name, false) }
+
+.row.prepend-top-default
+ .col-md-4
+ = render 'shared/milestones/issuables', args.merge(title: 'Unstarted Issues (open and unassigned)', issuables: issues.opened.unassigned, id: 'unassigned', show_counter: true)
+ .col-md-4
+ = render 'shared/milestones/issuables', args.merge(title: 'Ongoing Issues (open and assigned)', issuables: issues.opened.assigned, id: 'ongoing', show_counter: true)
+ .col-md-4
+ = render 'shared/milestones/issuables', args.merge(title: 'Completed Issues (closed)', issuables: issues.closed, id: 'closed', show_counter: true)
diff --git a/app/views/shared/milestones/_labels_tab.html.haml b/app/views/shared/milestones/_labels_tab.html.haml
new file mode 100644
index 00000000000..ba27bafd1bc
--- /dev/null
+++ b/app/views/shared/milestones/_labels_tab.html.haml
@@ -0,0 +1,18 @@
+%ul.bordered-list.manage-labels-list
+ - labels.each do |label|
+ - options = { milestone_title: @milestone.title, label_name: label.title }
+
+ %li
+ %span.label-row
+ = link_to milestones_label_path(options) do
+ - render_colored_label(label)
+ %span.prepend-left-10
+ = markdown(label.description, pipeline: :single_line)
+
+ .pull-right
+ %strong.issues-count
+ = link_to milestones_label_path(options.merge(state: 'opened')) do
+ - pluralize milestone_issues_by_label_count(@milestone, label, state: :opened), 'open issue'
+ %strong.issues-count
+ = link_to milestones_label_path(options.merge(state: 'closed')) do
+ - pluralize milestone_issues_by_label_count(@milestone, label, state: :closed), 'closed issue'
diff --git a/app/views/shared/milestones/_merge_requests_tab.haml b/app/views/shared/milestones/_merge_requests_tab.haml
new file mode 100644
index 00000000000..c29d8ee6737
--- /dev/null
+++ b/app/views/shared/milestones/_merge_requests_tab.haml
@@ -0,0 +1,12 @@
+- args = { show_project_name: local_assigns.fetch(:show_project_name, false),
+ show_full_project_name: local_assigns.fetch(:show_full_project_name, false) }
+
+.row.prepend-top-default
+ .col-md-3
+ = render 'shared/milestones/issuables', args.merge(title: 'Work in progress (open and unassigned)', issuables: merge_requests.opened.unassigned, id: 'unassigned')
+ .col-md-3
+ = render 'shared/milestones/issuables', args.merge(title: 'Waiting for merge (open and assigned)', issuables: merge_requests.opened.assigned, id: 'ongoing')
+ .col-md-3
+ = render 'shared/milestones/issuables', args.merge(title: 'Rejected (closed)', issuables: merge_requests.closed, id: 'closed')
+ .col-md-3
+ = render 'shared/milestones/issuables', args.merge(title: 'Merged', issuables: merge_requests.merged, id: 'merged', primary: true)
diff --git a/app/views/shared/milestones/_milestone.html.haml b/app/views/shared/milestones/_milestone.html.haml
new file mode 100644
index 00000000000..f01138af3f0
--- /dev/null
+++ b/app/views/shared/milestones/_milestone.html.haml
@@ -0,0 +1,45 @@
+- dashboard = local_assigns[:dashboard]
+- custom_dom_id = dom_id(@project ? milestone : milestone.milestones.first)
+
+%li{class: "milestone milestone-#{milestone.closed? ? 'closed' : 'open'}", id: custom_dom_id }
+ .row
+ .col-sm-6
+ %strong= link_to_gfm truncate(milestone.title, length: 100), milestone_path
+ .col-sm-6
+ .pull-right.light #{milestone.percent_complete}% complete
+ .row
+ .col-sm-6
+ = link_to pluralize(milestone.issues.size, 'Issue'), issues_path
+ &middot;
+ = link_to pluralize(milestone.merge_requests.size, 'Merge Request'), merge_requests_path
+ .col-sm-6= milestone_progress_bar(milestone)
+ - if milestone.is_a?(GlobalMilestone)
+ .row
+ .col-sm-6
+ .expiration= render('shared/milestone_expired', milestone: milestone)
+ .projects
+ - milestone.milestones.each do |milestone|
+ = link_to milestone_path(milestone) do
+ %span.label.label-gray
+ = dashboard ? milestone.project.name_with_namespace : milestone.project.name
+ - if @group
+ .col-sm-6
+ - if can?(current_user, :admin_milestones, @group)
+ - if milestone.closed?
+ = link_to 'Reopen Milestone', group_milestone_path(@group, milestone.safe_title, title: milestone.title, milestone: {state_event: :activate }), method: :put, class: "btn btn-xs btn-grouped btn-reopen"
+ - else
+ = link_to 'Close Milestone', group_milestone_path(@group, milestone.safe_title, title: milestone.title, milestone: {state_event: :close }), method: :put, class: "btn btn-xs btn-close"
+
+ - if @project
+ .row
+ .col-sm-6= render('shared/milestone_expired', milestone: milestone)
+ .col-sm-6
+ - if can?(current_user, :admin_milestone, milestone.project) and milestone.active?
+ = link_to edit_namespace_project_milestone_path(milestone.project.namespace, milestone.project, milestone), class: "btn btn-xs" do
+ = icon('pencil-square-o')
+ Edit
+ \
+ = link_to 'Close Milestone', namespace_project_milestone_path(@project.namespace, @project, milestone, milestone: {state_event: :close }), method: :put, remote: true, class: "btn btn-xs btn-close"
+ = link_to namespace_project_milestone_path(milestone.project.namespace, milestone.project, milestone), data: { confirm: 'Are you sure?' }, method: :delete, class: "btn btn-xs btn-remove" do
+ = icon('trash-o')
+ Delete
diff --git a/app/views/shared/milestones/_participants_tab.html.haml b/app/views/shared/milestones/_participants_tab.html.haml
new file mode 100644
index 00000000000..67ae85ac276
--- /dev/null
+++ b/app/views/shared/milestones/_participants_tab.html.haml
@@ -0,0 +1,8 @@
+%ul.bordered-list
+ - users.each do |user|
+ %li
+ = link_to user, title: user.name, class: "darken" do
+ = image_tag avatar_icon(user, 32), class: "avatar s32"
+ %strong= truncate(user.name, lenght: 40)
+ %br
+ %small.cgray= user.username
diff --git a/app/views/shared/milestones/_summary.html.haml b/app/views/shared/milestones/_summary.html.haml
new file mode 100644
index 00000000000..59d4ae29f79
--- /dev/null
+++ b/app/views/shared/milestones/_summary.html.haml
@@ -0,0 +1,28 @@
+- project = local_assigns[:project]
+
+.context.prepend-top-default
+ .milestone-summary
+ %h4 Progress
+ %strong= milestone.issues.size
+ issues:
+ %span.milestone-stat
+ %strong= milestone.issues.opened.size
+ open and
+ %strong= milestone.issues.closed.size
+ closed
+ %span.milestone-stat
+ %strong== #{milestone.percent_complete}%
+ complete
+
+ %span.milestone-stat
+ %span.remaining-days= milestone_remaining_days(milestone)
+ %span.pull-right.tab-issues-buttons
+ - if project && can?(current_user, :create_issue, project)
+ = link_to new_namespace_project_issue_path(project.namespace, project, issue: { milestone_id: milestone.id }), class: "btn btn-grouped", title: "New Issue" do
+ %i.fa.fa-plus
+ New Issue
+ = link_to 'Browse Issues', milestones_browse_issuables_path(milestone, type: :issues), class: "btn btn-grouped"
+ %span.pull-right.tab-merge-requests-buttons.hidden
+ = link_to 'Browse Merge Requests', milestones_browse_issuables_path(milestone, type: :merge_requests), class: "btn btn-grouped"
+
+ = milestone_progress_bar(milestone)
diff --git a/app/views/shared/milestones/_tabs.html.haml b/app/views/shared/milestones/_tabs.html.haml
new file mode 100644
index 00000000000..57d7ee85a3b
--- /dev/null
+++ b/app/views/shared/milestones/_tabs.html.haml
@@ -0,0 +1,30 @@
+%ul.nav-links.no-top.no-bottom
+ %li.active
+ = link_to '#tab-issues', 'data-toggle' => 'tab', 'data-show' => '.tab-issues-buttons' do
+ Issues
+ %span.badge= milestone.issues.size
+ %li
+ = link_to '#tab-merge-requests', 'data-toggle' => 'tab', 'data-show' => '.tab-merge-requests-buttons' do
+ Merge Requests
+ %span.badge= milestone.merge_requests.size
+ %li
+ = link_to '#tab-participants', 'data-toggle' => 'tab' do
+ Participants
+ %span.badge= milestone.participants.count
+ %li
+ = link_to '#tab-labels', 'data-toggle' => 'tab' do
+ Labels
+ %span.badge= milestone.labels.count
+
+- show_project_name = local_assigns.fetch(:show_project_name, false)
+- show_full_project_name = local_assigns.fetch(:show_full_project_name, false)
+
+.tab-content.milestone-content
+ .tab-pane.active#tab-issues
+ = render 'shared/milestones/issues_tab', issues: milestone.issues, show_project_name: show_project_name, show_full_project_name: show_full_project_name
+ .tab-pane#tab-merge-requests
+ = render 'shared/milestones/merge_requests_tab', merge_requests: milestone.merge_requests, show_project_name: show_project_name, show_full_project_name: show_full_project_name
+ .tab-pane#tab-participants
+ = render 'shared/milestones/participants_tab', users: milestone.participants
+ .tab-pane#tab-labels
+ = render 'shared/milestones/labels_tab', labels: milestone.labels
diff --git a/app/views/shared/milestones/_top.html.haml b/app/views/shared/milestones/_top.html.haml
new file mode 100644
index 00000000000..4cf1d948b5b
--- /dev/null
+++ b/app/views/shared/milestones/_top.html.haml
@@ -0,0 +1,58 @@
+- page_title milestone.title, "Milestones"
+
+- group = local_assigns[:group]
+
+.detail-page-header
+ .status-box{ class: "status-box-#{milestone.closed? ? 'closed' : 'open'}" }
+ - if milestone.closed?
+ Closed
+ - elsif milestone.expired?
+ Expired
+ - else
+ Open
+ %span.identifier
+ Milestone #{milestone.title}
+ - if milestone.expires_at
+ %span.creator
+ &middot;
+ = milestone.expires_at
+ - if group
+ .pull-right
+ - if can?(current_user, :admin_milestones, group)
+ - if milestone.active?
+ = link_to 'Close Milestone', group_milestone_path(group, milestone.safe_title, title: milestone.title, milestone: {state_event: :close }), method: :put, class: "btn btn-grouped btn-close"
+ - else
+ = link_to 'Reopen Milestone', group_milestone_path(group, milestone.safe_title, title: milestone.title, milestone: {state_event: :activate }), method: :put, class: "btn btn-grouped btn-reopen"
+
+.detail-page-description.gray-content-block.second-block
+ %h2.title
+ = markdown escape_once(milestone.title), pipeline: :single_line
+
+- if milestone.complete? && milestone.active?
+ .alert.alert-success.prepend-top-default
+ - close_msg = group ? 'You may close the milestone now.' : 'Navigate to the project to close the milestone.'
+ %span All issues for this milestone are closed. #{close_msg}
+
+.table-holder
+ %table.table
+ %thead
+ %tr
+ %th Project
+ %th Open issues
+ %th State
+ %th Due date
+ - milestone.milestones.each do |ms|
+ %tr
+ %td
+ - project_name = group ? ms.project.name : ms.project.name_with_namespace
+ = link_to project_name, namespace_project_milestone_path(ms.project.namespace, ms.project, ms)
+ %td
+ = ms.issues.opened.count
+ %td
+ - if ms.closed?
+ Closed
+ - else
+ Open
+ %td
+ = ms.expires_at
+
diff --git a/app/views/shared/projects/_list.html.haml b/app/views/shared/projects/_list.html.haml
index 75684b972f1..2e08bb2ac08 100644
--- a/app/views/shared/projects/_list.html.haml
+++ b/app/views/shared/projects/_list.html.haml
@@ -6,25 +6,19 @@
- ci = false unless local_assigns[:ci] == true
- skip_namespace = false unless local_assigns[:skip_namespace] == true
- show_last_commit_as_description = false unless local_assigns[:show_last_commit_as_description] == true
+- remote = false unless local_assigns[:remote] == true
-%ul.projects-list
+.projects-list-holder
- if projects.any?
- - projects.each_with_index do |project, i|
- - css_class = (i >= projects_limit) ? 'hide' : nil
- = render "shared/projects/project", project: project, skip_namespace: skip_namespace,
- avatar: avatar, stars: stars, css_class: css_class, ci: ci, use_creator_avatar: use_creator_avatar,
- forks: forks, show_last_commit_as_description: show_last_commit_as_description
-
- - if projects.size > projects_limit && projects.kind_of?(Array)
- %li.bottom.center
- .light
- #{projects_limit} of #{pluralize(projects.count, 'project')} displayed.
- = link_to '#', class: 'js-expand' do
- Show all
- = paginate projects, theme: "gitlab" if projects.respond_to? :total_pages
+ %ul.projects-list.content-list
+ - projects.each_with_index do |project, i|
+ - css_class = (i >= projects_limit) ? 'hide' : nil
+ = render "shared/projects/project", project: project, skip_namespace: skip_namespace,
+ avatar: avatar, stars: stars, css_class: css_class, ci: ci, use_creator_avatar: use_creator_avatar,
+ forks: forks, show_last_commit_as_description: show_last_commit_as_description
+ = paginate(projects, remote: remote, theme: "gitlab") if projects.respond_to? :total_pages
- else
- %h3 No projects found
+ .nothing-here-block No projects found
:javascript
- new ProjectsList();
- Dashboard.init();
+ ProjectsList.init();
diff --git a/app/views/shared/projects/_project.html.haml b/app/views/shared/projects/_project.html.haml
index 00bf9dcd2d5..99e48e86e38 100644
--- a/app/views/shared/projects/_project.html.haml
+++ b/app/views/shared/projects/_project.html.haml
@@ -4,7 +4,7 @@
- ci = false unless local_assigns[:ci] == true
- skip_namespace = false unless local_assigns[:skip_namespace] == true
- css_class = '' unless local_assigns[:css_class]
-- show_last_commit_as_description = false unless local_assigns[:show_last_commit_as_description] == true
+- show_last_commit_as_description = false unless local_assigns[:show_last_commit_as_description] == true && project.commit
- css_class += " no-description" if project.description.blank? && !show_last_commit_as_description
- ci_commit = project.ci_commit(project.commit.sha) if ci && !project.empty_repo? && project.commit
- cache_key = [project.namespace, project, controller.controller_name, controller.action_name, current_application_settings, 'v2.2']
@@ -19,7 +19,7 @@
= image_tag avatar_icon(project.creator.email, 40), class: "avatar s40", alt:''
- else
= project_icon(project, alt: '', class: 'avatar project-avatar s40')
- %span.project-full-name
+ %span.project-full-name.title
%span.namespace-name
- if project.namespace && !skip_namespace
= project.namespace.human_name
@@ -27,7 +27,7 @@
%span.project-name.filter-title
= project.name
- .project-controls
+ .controls
- if ci_commit
%span
= render_ci_status(ci_commit)
@@ -39,10 +39,13 @@
%span
= icon('star')
= project.star_count
+ %span.visibility-icon.has_tooltip{data: { container: 'body', placement: 'left' },
+ title: "#{visibility_level_label(project.visibility_level)} - #{project_visibility_level_description(project.visibility_level)}"}
+ = visibility_level_icon(project.visibility_level, fw: false)
- if show_last_commit_as_description
- .project-description
+ .description
= link_to_gfm project.commit.title, namespace_project_commit_path(project.namespace, project, project.commit),
class: "commit-row-message"
- elsif project.description.present?
- .project-description
+ .description
= markdown(project.description, pipeline: :description)
diff --git a/app/views/shared/snippets/_snippet.html.haml b/app/views/shared/snippets/_snippet.html.haml
index c6294caddc7..a316a085107 100644
--- a/app/views/shared/snippets/_snippet.html.haml
+++ b/app/views/shared/snippets/_snippet.html.haml
@@ -1,10 +1,12 @@
%li.snippet-row
+ = image_tag avatar_icon(snippet.author_email), class: "avatar s40 hidden-xs", alt: ''
+
.snippet-title
- = link_to reliable_snippet_path(snippet) do
+ = link_to reliable_snippet_path(snippet), class: 'title' do
= truncate(snippet.title, length: 60)
- if snippet.private?
%span.label.label-gray
- %i.fa.fa-lock
+ = icon('lock')
private
%span.monospace.pull-right
= snippet.file_name
@@ -15,6 +17,5 @@
.snippet-info
= link_to user_snippets_path(snippet.author) do
- = image_tag avatar_icon(snippet.author_email), class: "avatar s24", alt: ''
= snippet.author_name
authored #{time_ago_with_tooltip(snippet.created_at)}
diff --git a/app/views/snippets/_snippets.html.haml b/app/views/snippets/_snippets.html.haml
index d9aa4dd1d2e..80a3e731e1d 100644
--- a/app/views/snippets/_snippets.html.haml
+++ b/app/views/snippets/_snippets.html.haml
@@ -1,4 +1,4 @@
-%ul.bordered-list
+%ul.content-list
= render partial: 'shared/snippets/snippet', collection: @snippets
- if @snippets.empty?
%li
diff --git a/app/views/users/show.html.haml b/app/views/users/show.html.haml
index 3bfd781e51d..bca816f22cb 100644
--- a/app/views/users/show.html.haml
+++ b/app/views/users/show.html.haml
@@ -8,115 +8,110 @@
= render 'shared/show_aside'
-.cover-block
- .cover-controls
- - if @user == current_user
- = link_to profile_path, class: 'btn btn-gray' do
- = icon('pencil')
- - elsif current_user
- %span.report-abuse
- - if @user.abuse_report
- %button.btn.btn-danger{ title: 'Already reported for abuse',
- data: { toggle: 'tooltip', placement: 'left', container: 'body' }}
- = icon('exclamation-circle')
- - else
- = link_to new_abuse_report_path(user_id: @user.id, ref_url: request.referrer), class: 'btn btn-gray',
- title: 'Report abuse', data: {toggle: 'tooltip', placement: 'left', container: 'body'} do
- = icon('exclamation-circle')
- - if current_user
- &nbsp;
- = link_to user_path(@user, :atom, { private_token: current_user.private_token }), class: 'btn btn-gray' do
- = icon('rss')
-
- .avatar-holder
- = link_to avatar_icon(@user, 400), target: '_blank' do
- = image_tag avatar_icon(@user, 90), class: "avatar s90", alt: ''
- .cover-title
- = @user.name
-
- .cover-desc
- %span
- @#{@user.username}.
+.user-profile
+ .cover-block
+ .cover-controls
+ - if @user == current_user
+ = link_to profile_path, class: 'btn btn-gray' do
+ = icon('pencil')
+ - elsif current_user
+ %span.report-abuse
+ - if @user.abuse_report
+ %button.btn.btn-danger{ title: 'Already reported for abuse',
+ data: { toggle: 'tooltip', placement: 'left', container: 'body' }}
+ = icon('exclamation-circle')
+ - else
+ = link_to new_abuse_report_path(user_id: @user.id, ref_url: request.referrer), class: 'btn btn-gray',
+ title: 'Report abuse', data: {toggle: 'tooltip', placement: 'left', container: 'body'} do
+ = icon('exclamation-circle')
+ - if current_user
+ &nbsp;
+ = link_to user_path(@user, :atom, { private_token: current_user.private_token }), class: 'btn btn-gray' do
+ = icon('rss')
+
+ .avatar-holder
+ = link_to avatar_icon(@user, 400), target: '_blank' do
+ = image_tag avatar_icon(@user, 90), class: "avatar s90", alt: ''
+ .cover-title
+ = @user.name
+
+ .cover-desc
+ %span.middle-dot-divider
+ @#{@user.username}
+ %span.middle-dot-divider
+ Member since #{@user.created_at.to_s(:medium)}
+
- if @user.bio.present?
- %span
- #{@user.bio}.
- %span
- Member since #{@user.created_at.to_s(:medium)}
-
- .cover-desc
- - unless @user.public_email.blank?
- .profile-link-holder
- = link_to @user.public_email, "mailto:#{@user.public_email}"
- - unless @user.skype.blank?
- .profile-link-holder
- = link_to "skype:#{@user.skype}", title: "Skype" do
- = icon('skype')
- - unless @user.linkedin.blank?
- .profile-link-holder
- = link_to "https://www.linkedin.com/in/#{@user.linkedin}", title: "LinkedIn" do
- = icon('linkedin-square')
- - unless @user.twitter.blank?
- .profile-link-holder
- = link_to "https://twitter.com/#{@user.twitter}", title: "Twitter" do
- = icon('twitter-square')
- - unless @user.website_url.blank?
- .profile-link-holder
- = link_to @user.short_website_url, @user.full_website_url
- - unless @user.location.blank?
- .profile-link-holder
- = icon('map-marker')
- = @user.location
-
- %ul.nav-links.center
- %li.active
- = link_to "#activity", 'data-toggle' => 'tab' do
- Activity
- - if @groups.any?
- %li
- = link_to "#groups", 'data-toggle' => 'tab' do
+ .cover-desc
+ %p.profile-user-bio
+ = @user.bio
+
+ .cover-desc
+ - unless @user.public_email.blank?
+ .profile-link-holder.middle-dot-divider
+ = link_to @user.public_email, "mailto:#{@user.public_email}"
+ - unless @user.skype.blank?
+ .profile-link-holder.middle-dot-divider
+ = link_to "skype:#{@user.skype}", title: "Skype" do
+ = icon('skype')
+ - unless @user.linkedin.blank?
+ .profile-link-holder.middle-dot-divider
+ = link_to "https://www.linkedin.com/in/#{@user.linkedin}", title: "LinkedIn" do
+ = icon('linkedin-square')
+ - unless @user.twitter.blank?
+ .profile-link-holder.middle-dot-divider
+ = link_to "https://twitter.com/#{@user.twitter}", title: "Twitter" do
+ = icon('twitter-square')
+ - unless @user.website_url.blank?
+ .profile-link-holder.middle-dot-divider
+ = link_to @user.short_website_url, @user.full_website_url
+ - unless @user.location.blank?
+ .profile-link-holder.middle-dot-divider
+ = icon('map-marker')
+ = @user.location
+
+ %ul.nav-links.center.user-profile-nav
+ %li.activity-tab
+ = link_to user_calendar_activities_path, data: {target: 'div#activity', action: 'activity', toggle: 'tab'} do
+ Activity
+ %li.groups-tab
+ = link_to user_groups_path, data: {target: 'div#groups', action: 'groups', toggle: 'tab'} do
Groups
- - if @contributed_projects.present?
- %li
- = link_to "#contributed", 'data-toggle' => 'tab' do
+ %li.contributed-tab
+ = link_to user_contributed_projects_path, data: {target: 'div#contributed', action: 'contributed', toggle: 'tab'} do
Contributed projects
- - if @projects.present?
- %li
- = link_to "#personal", 'data-toggle' => 'tab' do
+ %li.projects-tab
+ = link_to user_projects_path, data: {target: 'div#projects', action: 'projects', toggle: 'tab'} do
Personal projects
-%div{ class: container_class }
- .tab-content
- .tab-pane.active#activity
- .gray-content-block.white.second-block
- %div{ class: container_class }
- .user-calendar
- %h4.center.light
- %i.fa.fa-spinner.fa-spin
- .user-calendar-activities
+ %div{ class: container_class }
+ .tab-content
+ #activity.tab-pane
+ .gray-content-block.white.second-block
+ %div{ class: container_class }
+ .user-calendar{data: {href: user_calendar_path}}
+ %h4.center.light
+ %i.fa.fa-spinner.fa-spin
+ .user-calendar-activities
+ .content_list{ data: {href: user_path} }
+ = spinner
- .content_list
- = spinner
+ #groups.tab-pane
+ - # This tab is always loaded via AJAX
+
+ #contributed.contributed-projects.tab-pane
+ - # This tab is always loaded via AJAX
- - if @groups.any?
- .tab-pane#groups
- %ul.content-list
- - @groups.each do |group|
- = render 'shared/groups/group', group: group
-
- - if @contributed_projects.present?
- .tab-pane#contributed
- .contributed-projects
- = render 'shared/projects/list',
- projects: @contributed_projects.sort_by(&:star_count).reverse,
- projects_limit: 10, stars: true, avatar: true
-
- - if @projects.present?
- .tab-pane#personal
- .personal-projects
- = render 'shared/projects/list',
- projects: @projects.sort_by(&:star_count).reverse,
- projects_limit: 10, stars: true, avatar: true
+ #projects.tab-pane
+ - # This tab is always loaded via AJAX
+
+ .loading-status
+ = spinner
:javascript
- $(".user-calendar").load("#{user_calendar_path}");
+ var userProfile;
+
+ userProfile = new User({
+ action: "#{controller.action_name}"
+ });
diff --git a/app/views/votes/_votes_block.html.haml b/app/views/votes/_votes_block.html.haml
index 91c5b7eac5e..176fd29cb57 100644
--- a/app/views/votes/_votes_block.html.haml
+++ b/app/views/votes/_votes_block.html.haml
@@ -9,15 +9,6 @@
.awards-controls
%a.add-award{"href" => "#"}
= icon('smile-o')
- .emoji-menu
- .emoji-menu-content
- = text_field_tag :emoji_search, "", class: "emoji-search search-input form-control"
- - AwardEmoji.emoji_by_category.each do |category, emojis|
- %h5= AwardEmoji::CATEGORIES[category]
- %ul
- - emojis.each do |emoji|
- %li
- = emoji_icon(emoji["name"], emoji["unicode"], emoji["aliases"])
- if current_user
:javascript
diff --git a/app/workers/irker_worker.rb b/app/workers/irker_worker.rb
index 2d44d8d4dc6..605ec4f04e5 100644
--- a/app/workers/irker_worker.rb
+++ b/app/workers/irker_worker.rb
@@ -141,7 +141,7 @@ class IrkerWorker
end
def files_count(commit)
- files = "#{commit.diffs.count} file"
+ files = "#{commit.diffs.real_size} file"
files += 's' if commit.diffs.count > 1
files
end
diff --git a/app/workers/post_receive.rb b/app/workers/post_receive.rb
index 994b8e8ed38..14d7813412e 100644
--- a/app/workers/post_receive.rb
+++ b/app/workers/post_receive.rb
@@ -38,7 +38,7 @@ class PostReceive
if Gitlab::Git.tag_ref?(ref)
GitTagPushService.new.execute(project, @user, oldrev, newrev, ref)
else
- GitPushService.new.execute(project, @user, oldrev, newrev, ref)
+ GitPushService.new(project, @user, oldrev: oldrev, newrev: newrev, ref: ref).execute
end
end
end
diff --git a/app/workers/repository_fork_worker.rb b/app/workers/repository_fork_worker.rb
index 2f991c52339..21d311579e3 100644
--- a/app/workers/repository_fork_worker.rb
+++ b/app/workers/repository_fork_worker.rb
@@ -27,6 +27,7 @@ class RepositoryForkWorker
return
end
+ project.repository.after_import
project.import_finish
end
end
diff --git a/app/workers/repository_import_worker.rb b/app/workers/repository_import_worker.rb
index e295a9ddd14..2937493c614 100644
--- a/app/workers/repository_import_worker.rb
+++ b/app/workers/repository_import_worker.rb
@@ -18,6 +18,7 @@ class RepositoryImportWorker
return
end
+ project.repository.after_import
project.import_finish
end
end
diff --git a/config/application.rb b/config/application.rb
index 1e9ec74cdbf..7fd75ebe69e 100644
--- a/config/application.rb
+++ b/config/application.rb
@@ -6,6 +6,8 @@ I18n.config.enforce_available_locales = false
Bundler.require(:default, Rails.env)
module Gitlab
+ REDIS_CACHE_NAMESPACE = 'cache:gitlab'
+
class Application < Rails::Application
# Settings in config/environments/* take precedence over those specified here.
# Application configuration should go into files in config/initializers
@@ -43,8 +45,8 @@ module Gitlab
# Enable the asset pipeline
config.assets.enabled = true
- config.assets.paths << Emoji.images_path
- config.assets.precompile << "emoji/*.png"
+ config.assets.paths << Gemojione.index.images_path
+ config.assets.precompile << "*.png"
config.assets.precompile << "print.css"
# Version of your assets, change this if you want to expire all your assets
@@ -52,14 +54,6 @@ module Gitlab
config.action_view.sanitized_allowed_protocols = %w(smb)
- # Relative URL support
- # WARNING: We recommend using an FQDN to host GitLab in a root path instead
- # of using a relative URL.
- # Documentation: http://doc.gitlab.com/ce/install/relative_url.html
- # Uncomment and customize the following line to run in a non-root path
- #
- # config.relative_url_root = "/gitlab"
-
config.middleware.use Rack::Attack
# Allow access to GitLab API from other domains
@@ -89,7 +83,7 @@ module Gitlab
redis_config_hash[:path] = redis_uri.path
end
- redis_config_hash[:namespace] = 'cache:gitlab'
+ redis_config_hash[:namespace] = REDIS_CACHE_NAMESPACE
redis_config_hash[:expires_in] = 2.weeks # Cache should not grow forever
config.cache_store = :redis_store, redis_config_hash
@@ -99,5 +93,9 @@ module Gitlab
# This is needed for gitlab-shell
ENV['GITLAB_PATH_OUTSIDE_HOOK'] = ENV['PATH']
+
+ config.generators do |g|
+ g.factory_girl false
+ end
end
end
diff --git a/config/gitlab.yml.example b/config/gitlab.yml.example
index faf05ecd466..05f127d622a 100644
--- a/config/gitlab.yml.example
+++ b/config/gitlab.yml.example
@@ -288,15 +288,22 @@ production: &base
# auto_sign_in_with_provider: saml
# CAUTION!
- # This allows users to login without having a user account first (default: false).
+ # This allows users to login without having a user account first. Define the allowed providers
+ # using an array, e.g. ["saml", "twitter"], or as true/false to allow all providers or none.
# User accounts will be created automatically when authentication was successful.
- allow_single_sign_on: false
+ allow_single_sign_on: ["saml"]
+
# Locks down those users until they have been cleared by the admin (default: true).
block_auto_created_users: true
# Look up new users in LDAP servers. If a match is found (same uid), automatically
# link the omniauth identity with the LDAP account. (default: false)
auto_link_ldap_user: false
+ # Allow users with existing accounts to login and auto link their account via SAML
+ # login, without having to do a manual login first and manually add SAML
+ # (default: false)
+ auto_link_saml_user: false
+
## Auth providers
# Uncomment the following lines and fill in the data of the auth provider you want to use
# If your favorite auth provider is not listed you can use others:
diff --git a/config/initializers/1_settings.rb b/config/initializers/1_settings.rb
index d8170557f7e..626268d7648 100644
--- a/config/initializers/1_settings.rb
+++ b/config/initializers/1_settings.rb
@@ -131,6 +131,7 @@ Settings.omniauth['auto_sign_in_with_provider'] = false if Settings.omniauth['au
Settings.omniauth['allow_single_sign_on'] = false if Settings.omniauth['allow_single_sign_on'].nil?
Settings.omniauth['block_auto_created_users'] = true if Settings.omniauth['block_auto_created_users'].nil?
Settings.omniauth['auto_link_ldap_user'] = false if Settings.omniauth['auto_link_ldap_user'].nil?
+Settings.omniauth['auto_link_saml_user'] = false if Settings.omniauth['auto_link_saml_user'].nil?
Settings.omniauth['providers'] ||= []
Settings.omniauth['cas3'] ||= Settingslogic.new({})
@@ -206,11 +207,7 @@ Settings.gitlab_ci['builds_path'] = File.expand_path(Settings.gitlab_c
# Reply by email
#
Settings['incoming_email'] ||= Settingslogic.new({})
-Settings.incoming_email['enabled'] = false if Settings.incoming_email['enabled'].nil?
-Settings.incoming_email['port'] = 143 if Settings.incoming_email['port'].nil?
-Settings.incoming_email['ssl'] = false if Settings.incoming_email['ssl'].nil?
-Settings.incoming_email['start_tls'] = false if Settings.incoming_email['start_tls'].nil?
-Settings.incoming_email['mailbox'] = "inbox" if Settings.incoming_email['mailbox'].nil?
+Settings.incoming_email['enabled'] = false if Settings.incoming_email['enabled'].nil?
#
# Build Artifacts
diff --git a/config/initializers/2_app.rb b/config/initializers/2_app.rb
index 35b150c9929..bd74f90e7d2 100644
--- a/config/initializers/2_app.rb
+++ b/config/initializers/2_app.rb
@@ -3,6 +3,6 @@ module Gitlab
Settings
end
- VERSION = File.read(Rails.root.join("VERSION")).strip
- REVISION = Gitlab::Popen.popen(%W(#{config.git.bin_path} log --pretty=format:%h -n 1)).first.chomp
+ VERSION = File.read(Rails.root.join("VERSION")).strip.freeze
+ REVISION = Gitlab::Popen.popen(%W(#{config.git.bin_path} log --pretty=format:%h -n 1)).first.chomp.freeze
end
diff --git a/config/initializers/relative_url.rb.sample b/config/initializers/relative_url.rb.sample
new file mode 100644
index 00000000000..125297d5385
--- /dev/null
+++ b/config/initializers/relative_url.rb.sample
@@ -0,0 +1,10 @@
+# Relative URL support
+# WARNING: We recommend using an FQDN to host GitLab in a root path instead
+# of using a relative URL.
+# Documentation: http://doc.gitlab.com/ce/install/relative_url.html
+# Copy this file to relative_url.rb and customize it to run in a non-root path
+#
+
+Rails.application.configure do
+ config.relative_url_root = "/gitlab"
+end
diff --git a/config/initializers/sentry.rb b/config/initializers/sentry.rb
index d0630b9fa07..e87899b2d5c 100644
--- a/config/initializers/sentry.rb
+++ b/config/initializers/sentry.rb
@@ -14,6 +14,7 @@ if Rails.env.production?
if sentry_enabled
Raven.configure do |config|
config.dsn = current_application_settings.sentry_dsn
+ config.release = Gitlab::REVISION
end
end
end
diff --git a/config/mail_room.yml b/config/mail_room.yml
index 42f6f74c465..f266a70ee0d 100644
--- a/config/mail_room.yml
+++ b/config/mail_room.yml
@@ -1,39 +1,52 @@
:mailboxes:
<%
-require_relative 'config/environment.rb'
-
-if Gitlab::IncomingEmail.enabled?
- config = Gitlab::IncomingEmail.config
-
- redis_config_file = "config/resque.yml"
- redis_url =
- if File.exists?(redis_config_file)
- YAML.load_file(redis_config_file)[Rails.env]
- else
- "redis://localhost:6379"
- end
- %>
- -
- :host: <%= config.host.to_json %>
- :port: <%= config.port.to_json %>
- :ssl: <%= config.ssl.to_json %>
- :start_tls: <%= config.start_tls.to_json %>
- :email: <%= config.user.to_json %>
- :password: <%= config.password.to_json %>
-
- :name: <%= config.mailbox.to_json %>
-
- :delete_after_delivery: true
-
- :delivery_method: sidekiq
- :delivery_options:
- :redis_url: <%= redis_url.to_json %>
- :namespace: resque:gitlab
- :queue: incoming_email
- :worker: EmailReceiverWorker
-
- :arbitration_method: redis
- :arbitration_options:
- :redis_url: <%= redis_url.to_json %>
- :namespace: mail_room:gitlab
+require "yaml"
+require "json"
+
+rails_env = ENV["RAILS_ENV"] || ENV["RACK_ENV"] || "development"
+
+config_file = ENV["MAIL_ROOM_GITLAB_CONFIG_FILE"] || "config/gitlab.yml"
+if File.exists?(config_file)
+ all_config = YAML.load_file(config_file)[rails_env]
+
+ config = all_config["incoming_email"] || {}
+ config['enabled'] = false if config['enabled'].nil?
+ config['port'] = 143 if config['port'].nil?
+ config['ssl'] = false if config['ssl'].nil?
+ 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}')
+ redis_config_file = "config/resque.yml"
+ redis_url =
+ if File.exists?(redis_config_file)
+ YAML.load_file(redis_config_file)[rails_env]
+ else
+ "redis://localhost:6379"
+ end
+ %>
+ -
+ :host: <%= config['host'].to_json %>
+ :port: <%= config['port'].to_json %>
+ :ssl: <%= config['ssl'].to_json %>
+ :start_tls: <%= config['start_tls'].to_json %>
+ :email: <%= config['user'].to_json %>
+ :password: <%= config['password'].to_json %>
+
+ :name: <%= config['mailbox'].to_json %>
+
+ :delete_after_delivery: true
+
+ :delivery_method: sidekiq
+ :delivery_options:
+ :redis_url: <%= redis_url.to_json %>
+ :namespace: resque:gitlab
+ :queue: incoming_email
+ :worker: EmailReceiverWorker
+
+ :arbitration_method: redis
+ :arbitration_options:
+ :redis_url: <%= redis_url.to_json %>
+ :namespace: mail_room:gitlab
+ <% end %>
<% end %>
diff --git a/config/newrelic.yml b/config/newrelic.yml
new file mode 100644
index 00000000000..9ef922a38d9
--- /dev/null
+++ b/config/newrelic.yml
@@ -0,0 +1,16 @@
+# New Relic configuration file
+#
+# This file is here to make sure the New Relic gem stays
+# quiet by default.
+#
+# To enable and configure New Relic, please use
+# environment variables, e.g. NEW_RELIC_ENABLED=true
+
+production:
+ enabled: false
+
+development:
+ enabled: false
+
+test:
+ enabled: false
diff --git a/config/routes.rb b/config/routes.rb
index 507bcbc53d7..a918b5bd3f0 100644
--- a/config/routes.rb
+++ b/config/routes.rb
@@ -43,6 +43,8 @@ Rails.application.routes.draw do
get '/autocomplete/users' => 'autocomplete#users'
get '/autocomplete/users/:id' => 'autocomplete#user'
+ # Emojis
+ resources :emojis, only: :index
# Search
get 'search' => 'search#show'
@@ -154,6 +156,11 @@ Rails.application.routes.draw do
to: "uploads#show",
constraints: { model: /note|user|group|project/, mounted_as: /avatar|attachment/, filename: /[^\/]+/ }
+ # Appearance
+ get ":model/:mounted_as/:id/:filename",
+ to: "uploads#show",
+ constraints: { model: /appearance/, mounted_as: /logo|header_logo/, filename: /.+/ }
+
# Project markdown uploads
get ":namespace_id/:project_id/:secret/:filename",
to: "projects/uploads#show",
@@ -251,6 +258,14 @@ Rails.application.routes.draw do
end
end
+ resource :appearances, path: 'appearance' do
+ member do
+ get :preview
+ delete :logo
+ delete :header_logos
+ end
+ end
+
resource :application_settings, only: [:show, :update] do
resources :services
put :reset_runners_token
@@ -299,7 +314,7 @@ Rails.application.routes.draw do
end
end
resource :preferences, only: [:show, :update]
- resources :keys
+ resources :keys, except: [:new]
resources :emails, only: [:index, :create, :destroy]
resource :avatar, only: [:destroy]
resource :two_factor_auth, only: [:new, :create, :destroy] do
@@ -317,6 +332,15 @@ Rails.application.routes.draw do
get 'u/:username/calendar_activities' => 'users#calendar_activities', as: :user_calendar_activities,
constraints: { username: /.*/ }
+ get 'u/:username/groups' => 'users#groups', as: :user_groups,
+ constraints: { username: /.*/ }
+
+ get 'u/:username/projects' => 'users#projects', as: :user_projects,
+ constraints: { username: /.*/ }
+
+ get 'u/:username/contributed' => 'users#contributed', as: :user_contributed_projects,
+ constraints: { username: /.*/ }
+
get '/u/:username' => 'users#show', as: :user,
constraints: { username: /[a-zA-Z.0-9_\-]+(?<!\.atom)/ }
@@ -334,6 +358,12 @@ Rails.application.routes.draw do
resources :groups, only: [:index]
resources :snippets, only: [:index]
+ resources :todos, only: [:index, :destroy] do
+ collection do
+ delete :destroy_all
+ end
+ end
+
resources :projects, only: [:index] do
collection do
get :starred
@@ -502,6 +532,7 @@ Rails.application.routes.draw do
get :builds
post :cancel_builds
post :retry_builds
+ post :revert
end
end
@@ -617,6 +648,7 @@ Rails.application.routes.draw do
get :status
post :cancel
post :retry
+ post :erase
end
resource :artifacts, only: [] do
diff --git a/config/sidekiq.yml.example b/config/sidekiq.yml.example
index c691db67c6c..714bc06cb24 100644
--- a/config/sidekiq.yml.example
+++ b/config/sidekiq.yml.example
@@ -1,2 +1,2 @@
---
+---
:concurrency: 5 \ No newline at end of file
diff --git a/db/fixtures/production/001_admin.rb b/db/fixtures/production/001_admin.rb
index 308b0528c9b..78746c83225 100644
--- a/db/fixtures/production/001_admin.rb
+++ b/db/fixtures/production/001_admin.rb
@@ -1,33 +1,38 @@
+user_args = {
+ email: ENV['GITLAB_ROOT_EMAIL'].presence || 'admin@example.com',
+ name: 'Administrator',
+ username: 'root',
+ admin: true
+}
+
if ENV['GITLAB_ROOT_PASSWORD'].blank?
- password = '5iveL!fe'
- expire_time = Time.now
+ user_args[:password_automatically_set] = true
+ user_args[:force_random_password] = true
else
- password = ENV['GITLAB_ROOT_PASSWORD']
- expire_time = nil
+ user_args[:password] = ENV['GITLAB_ROOT_PASSWORD']
end
-email = ENV['GITLAB_ROOT_EMAIL'].presence || 'admin@example.com'
-
-admin = User.create(
- email: email,
- name: "Administrator",
- username: 'root',
- password: password,
- password_expires_at: expire_time,
- theme_id: Gitlab::Themes::APPLICATION_DEFAULT
-
-)
+user = User.new(user_args)
+user.skip_confirmation!
-admin.projects_limit = 10000
-admin.admin = true
-admin.save!
-admin.confirm
+if user.save
+ puts "Administrator account created:".green
+ puts
+ puts "login: root".green
-if admin.valid?
-puts %Q[
-Administrator account created:
+ if user_args.key?(:password)
+ puts "password: #{user_args[:password]}".green
+ else
+ puts "password: You'll be prompted to create one on your first visit.".green
+ end
+ puts
+else
+ puts "Could not create the default administrator account:".red
+ puts
+ user.errors.full_messages.map do |message|
+ puts "--> #{message}".red
+ end
+ puts
-login.........root
-password......#{password}
-]
+ exit 1
end
diff --git a/db/migrate/20160129155512_add_merge_commit_sha_to_merge_requests.rb b/db/migrate/20160129155512_add_merge_commit_sha_to_merge_requests.rb
new file mode 100644
index 00000000000..f0d94226514
--- /dev/null
+++ b/db/migrate/20160129155512_add_merge_commit_sha_to_merge_requests.rb
@@ -0,0 +1,5 @@
+class AddMergeCommitShaToMergeRequests < ActiveRecord::Migration
+ def change
+ add_column :merge_requests, :merge_commit_sha, :string
+ end
+end
diff --git a/db/migrate/20160202091601_add_erasable_to_ci_build.rb b/db/migrate/20160202091601_add_erasable_to_ci_build.rb
new file mode 100644
index 00000000000..f9912f2274e
--- /dev/null
+++ b/db/migrate/20160202091601_add_erasable_to_ci_build.rb
@@ -0,0 +1,6 @@
+class AddErasableToCiBuild < ActiveRecord::Migration
+ def change
+ add_reference :ci_builds, :erased_by, references: :users, index: true
+ add_column :ci_builds, :erased_at, :datetime
+ end
+end
diff --git a/db/migrate/20160204144558_add_real_size_to_merge_request_diffs.rb b/db/migrate/20160204144558_add_real_size_to_merge_request_diffs.rb
new file mode 100644
index 00000000000..f996ae74dca
--- /dev/null
+++ b/db/migrate/20160204144558_add_real_size_to_merge_request_diffs.rb
@@ -0,0 +1,5 @@
+class AddRealSizeToMergeRequestDiffs < ActiveRecord::Migration
+ def change
+ add_column :merge_request_diffs, :real_size, :string
+ end
+end
diff --git a/db/migrate/20160212123307_create_tasks.rb b/db/migrate/20160212123307_create_tasks.rb
new file mode 100644
index 00000000000..c3f6f3abc26
--- /dev/null
+++ b/db/migrate/20160212123307_create_tasks.rb
@@ -0,0 +1,14 @@
+class CreateTasks < ActiveRecord::Migration
+ def change
+ create_table :tasks do |t|
+ t.references :user, null: false, index: true
+ t.references :project, null: false, index: true
+ t.references :target, polymorphic: true, null: false, index: true
+ t.integer :author_id, index: true
+ t.integer :action, null: false
+ t.string :state, null: false, index: true
+
+ t.timestamps
+ end
+ end
+end
diff --git a/db/migrate/20160217100506_add_description_to_label.rb b/db/migrate/20160217100506_add_description_to_label.rb
new file mode 100644
index 00000000000..eed6d1f236a
--- /dev/null
+++ b/db/migrate/20160217100506_add_description_to_label.rb
@@ -0,0 +1,5 @@
+class AddDescriptionToLabel < ActiveRecord::Migration
+ def change
+ add_column :labels, :description, :string
+ end
+end
diff --git a/db/migrate/20160217174422_add_note_to_tasks.rb b/db/migrate/20160217174422_add_note_to_tasks.rb
new file mode 100644
index 00000000000..da5cb2e05db
--- /dev/null
+++ b/db/migrate/20160217174422_add_note_to_tasks.rb
@@ -0,0 +1,5 @@
+class AddNoteToTasks < ActiveRecord::Migration
+ def change
+ add_reference :tasks, :note, index: true
+ end
+end
diff --git a/db/migrate/20160220123949_rename_tasks_to_todos.rb b/db/migrate/20160220123949_rename_tasks_to_todos.rb
new file mode 100644
index 00000000000..30c10d27146
--- /dev/null
+++ b/db/migrate/20160220123949_rename_tasks_to_todos.rb
@@ -0,0 +1,5 @@
+class RenameTasksToTodos < ActiveRecord::Migration
+ def change
+ rename_table :tasks, :todos
+ end
+end
diff --git a/db/migrate/20160222153918_create_appearances_ce.rb b/db/migrate/20160222153918_create_appearances_ce.rb
new file mode 100644
index 00000000000..bec66bcc71e
--- /dev/null
+++ b/db/migrate/20160222153918_create_appearances_ce.rb
@@ -0,0 +1,14 @@
+class CreateAppearancesCe < ActiveRecord::Migration
+ def change
+ unless table_exists?(:appearances)
+ create_table :appearances do |t|
+ t.string :title
+ t.text :description
+ t.string :header_logo
+ t.string :logo
+
+ t.timestamps null: false
+ end
+ end
+ end
+end
diff --git a/db/schema.rb b/db/schema.rb
index 689a8c3ecc5..71d9257a31e 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: 20160209130428) do
+ActiveRecord::Schema.define(version: 20160222153918) do
# These are extensions that must be enabled in order to support this database
enable_extension "plpgsql"
@@ -24,6 +24,15 @@ ActiveRecord::Schema.define(version: 20160209130428) do
t.datetime "updated_at"
end
+ create_table "appearances", force: :cascade do |t|
+ t.string "title"
+ t.text "description"
+ t.string "header_logo"
+ t.string "logo"
+ t.datetime "created_at", null: false
+ t.datetime "updated_at", null: false
+ end
+
create_table "application_settings", force: :cascade do |t|
t.integer "default_projects_limit"
t.boolean "signup_enabled"
@@ -129,6 +138,8 @@ ActiveRecord::Schema.define(version: 20160209130428) do
t.text "artifacts_file"
t.integer "gl_project_id"
t.text "artifacts_metadata"
+ t.integer "erased_by_id"
+ t.datetime "erased_at"
end
add_index "ci_builds", ["commit_id", "stage_idx", "created_at"], name: "index_ci_builds_on_commit_id_and_stage_idx_and_created_at", using: :btree
@@ -136,6 +147,7 @@ ActiveRecord::Schema.define(version: 20160209130428) do
add_index "ci_builds", ["commit_id", "type", "name", "ref"], name: "index_ci_builds_on_commit_id_and_type_and_name_and_ref", using: :btree
add_index "ci_builds", ["commit_id", "type", "ref"], name: "index_ci_builds_on_commit_id_and_type_and_ref", using: :btree
add_index "ci_builds", ["commit_id"], name: "index_ci_builds_on_commit_id", using: :btree
+ add_index "ci_builds", ["erased_by_id"], name: "index_ci_builds_on_erased_by_id", using: :btree
add_index "ci_builds", ["gl_project_id"], name: "index_ci_builds_on_gl_project_id", using: :btree
add_index "ci_builds", ["project_id", "commit_id"], name: "index_ci_builds_on_project_id_and_commit_id", using: :btree
add_index "ci_builds", ["project_id"], name: "index_ci_builds_on_project_id", using: :btree
@@ -442,7 +454,8 @@ ActiveRecord::Schema.define(version: 20160209130428) do
t.integer "project_id"
t.datetime "created_at"
t.datetime "updated_at"
- t.boolean "template", default: false
+ t.boolean "template", default: false
+ t.string "description"
end
add_index "labels", ["project_id"], name: "index_labels_on_project_id", using: :btree
@@ -496,6 +509,7 @@ ActiveRecord::Schema.define(version: 20160209130428) do
t.datetime "created_at"
t.datetime "updated_at"
t.string "base_commit_sha"
+ t.string "real_size"
end
add_index "merge_request_diffs", ["merge_request_id"], name: "index_merge_request_diffs_on_merge_request_id", unique: true, using: :btree
@@ -522,6 +536,7 @@ ActiveRecord::Schema.define(version: 20160209130428) do
t.text "merge_params"
t.boolean "merge_when_build_succeeds", default: false, null: false
t.integer "merge_user_id"
+ t.string "merge_commit_sha"
end
add_index "merge_requests", ["assignee_id"], name: "index_merge_requests_on_assignee_id", using: :btree
@@ -819,6 +834,26 @@ ActiveRecord::Schema.define(version: 20160209130428) do
add_index "tags", ["name"], name: "index_tags_on_name", unique: true, using: :btree
+ create_table "todos", force: :cascade do |t|
+ t.integer "user_id", null: false
+ t.integer "project_id", null: false
+ t.integer "target_id", null: false
+ t.string "target_type", null: false
+ t.integer "author_id"
+ t.integer "action", null: false
+ t.string "state", null: false
+ t.datetime "created_at"
+ t.datetime "updated_at"
+ t.integer "note_id"
+ end
+
+ add_index "todos", ["author_id"], name: "index_todos_on_author_id", using: :btree
+ add_index "todos", ["note_id"], name: "index_todos_on_note_id", using: :btree
+ add_index "todos", ["project_id"], name: "index_todos_on_project_id", using: :btree
+ add_index "todos", ["state"], name: "index_todos_on_state", using: :btree
+ add_index "todos", ["target_type", "target_id"], name: "index_todos_on_target_type_and_target_id", using: :btree
+ add_index "todos", ["user_id"], name: "index_todos_on_user_id", using: :btree
+
create_table "users", force: :cascade do |t|
t.string "email", default: "", null: false
t.string "encrypted_password", default: "", null: false
diff --git a/doc/README.md b/doc/README.md
index 5089e1e70f6..be6c5f96ea1 100644
--- a/doc/README.md
+++ b/doc/README.md
@@ -16,40 +16,42 @@
- [Web hooks](web_hooks/web_hooks.md) Let GitLab notify you when new code has been pushed to your project.
- [Workflow](workflow/README.md) Using GitLab functionality and importing projects from GitHub and SVN.
-## CI Documentation
+## CI User documentation
-- [Quick Start](ci/quick_start/README.md)
-- [Enable or disable GitLab CI](ci/enable_or_disable_ci.md)
-- [Configuring project (.gitlab-ci.yml)](ci/yaml/README.md)
-- [Configuring runner](ci/runners/README.md)
-- [Configuring deployment](ci/deployment/README.md)
-- [Using Docker Images](ci/docker/using_docker_images.md)
-- [Using Docker Build](ci/docker/using_docker_build.md)
-- [Using Variables](ci/variables/README.md)
-- [Using SSH keys](ci/ssh_keys/README.md)
+- [Get started with GitLab CI](ci/quick_start/README.md)
+- [Learn how to enable or disable GitLab CI](ci/enable_or_disable_ci.md)
+- [Learn how `.gitlab-ci.yml` works](ci/yaml/README.md)
+- [Configure a Runner, the application that runs your builds](ci/runners/README.md)
+- [Use Docker images with GitLab Runner](ci/docker/using_docker_images.md)
+- [Use CI to build Docker images](ci/docker/using_docker_build.md)
+- [Use variables in your `.gitlab-ci.yml`](ci/variables/README.md)
+- [Use SSH keys in your build environment](ci/ssh_keys/README.md)
+- [Trigger builds through the API](ci/triggers/README.md)
+- [Build artifacts](ci/build_artifacts/README.md)
- [User permissions](ci/permissions/README.md)
- [API](ci/api/README.md)
-- [Triggering builds through the API](ci/triggers/README.md)
-- [Build artifacts](ci/build_artifacts/README.md)
-### CI Languages
+### CI Examples
-- [Testing PHP](ci/languages/php.md)
+- [The .gitlab-ci.yml file for GitLab itself](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/.gitlab-ci.yml)
+- [Test your PHP applications](ci/examples/php.md)
+- [Test and deploy Ruby applications to Heroku](ci/examples/test-and-deploy-ruby-application-to-heroku.md)
+- [Test and deploy Python applications to Heroku](ci/examples/test-and-deploy-python-application-to-heroku.md)
+- [Test Clojure applications](ci/examples/test-clojure-application.md)
+- [Using `dpl` as deployment tool](ci/deployment/README.md)
+- Help your favorite programming language and GitLab by sending a merge request
+ with a guide for that language.
### CI Services
+GitLab CI uses the `services` keyword to define what docker containers should
+be linked with your base image. Below is a list of examples you may use:
+
- [Using MySQL](ci/services/mysql.md)
- [Using PostgreSQL](ci/services/postgres.md)
- [Using Redis](ci/services/redis.md)
- [Using Other Services](ci/docker/using_docker_images.md#how-to-use-other-images-as-services)
-### CI Examples
-
-- [Test and deploy Ruby applications to Heroku](ci/examples/test-and-deploy-ruby-application-to-heroku.md)
-- [Test and deploy Python applications to Heroku](ci/examples/test-and-deploy-python-application-to-heroku.md)
-- [Test Clojure applications](ci/examples/test-clojure-application.md)
-- Help your favorite programming language and GitLab by sending a merge request with a guide for that language.
-
## Administrator documentation
- [Custom git hooks](hooks/custom_hooks.md) Custom git hooks (on the filesystem) for when web hooks aren't enough.
diff --git a/doc/api/README.md b/doc/api/README.md
index 9f3ad126320..7629ef294ac 100644
--- a/doc/api/README.md
+++ b/doc/api/README.md
@@ -32,6 +32,7 @@ following locations:
- [Builds](builds.md)
- [Build triggers](build_triggers.md)
- [Build Variables](build_variables.md)
+- [Runners](runners.md)
## Authentication
diff --git a/doc/api/builds.md b/doc/api/builds.md
index 43edb40e911..d3ce72e59fc 100644
--- a/doc/api/builds.md
+++ b/doc/api/builds.md
@@ -34,6 +34,10 @@ Example of response
"coverage": null,
"created_at": "2015-12-24T15:51:21.802Z",
"download_url": null,
+ "artifacts_file": {
+ "filename": "artifacts.zip",
+ "size": 1000
+ },
"finished_at": "2015-12-24T17:54:27.895Z",
"id": 7,
"name": "teaspoon",
@@ -72,6 +76,7 @@ Example of response
"coverage": null,
"created_at": "2015-12-24T15:51:21.727Z",
"download_url": null,
+ "artifacts_file": null,
"finished_at": "2015-12-24T17:54:24.921Z",
"id": 6,
"name": "spinach:other",
@@ -135,6 +140,7 @@ Example of response
"coverage": null,
"created_at": "2016-01-11T10:13:33.506Z",
"download_url": null,
+ "artifacts_file": null,
"finished_at": "2016-01-11T10:14:09.526Z",
"id": 69,
"name": "rubocop",
@@ -159,6 +165,7 @@ Example of response
"coverage": null,
"created_at": "2015-12-24T15:51:21.957Z",
"download_url": null,
+ "artifacts_file": null,
"finished_at": "2015-12-24T17:54:33.913Z",
"id": 9,
"name": "brakeman",
@@ -220,6 +227,7 @@ Example of response
"coverage": null,
"created_at": "2015-12-24T15:51:21.880Z",
"download_url": null,
+ "artifacts_file": null,
"finished_at": "2015-12-24T17:54:31.198Z",
"id": 8,
"name": "rubocop",
@@ -247,6 +255,34 @@ Example of response
}
```
+## Get build artifacts
+
+> [Introduced][ce-2893] in GitLab 8.5
+
+Get build artifacts of a project
+
+```
+GET /projects/:id/builds/:build_id/artifacts
+```
+
+| Attribute | Type | Required | Description |
+|------------|---------|----------|---------------------|
+| `id` | integer | yes | The ID of a project |
+| `build_id` | integer | yes | The ID of a build |
+
+```
+curl -H "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v3/projects/1/builds/8/artifacts"
+```
+
+Response:
+
+| Status | Description |
+|-----------|---------------------------------|
+| 200 | Serves the artifacts file |
+| 404 | Build not found or no artifacts |
+
+[ce-2893]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/2893
+
## Cancel a build
Cancel a single build of a project
@@ -280,6 +316,7 @@ Example of response
"coverage": null,
"created_at": "2016-01-11T10:13:33.506Z",
"download_url": null,
+ "artifacts_file": null,
"finished_at": "2016-01-11T10:14:09.526Z",
"id": 69,
"name": "rubocop",
@@ -326,6 +363,7 @@ Example of response
"coverage": null,
"created_at": "2016-01-11T10:13:33.506Z",
"download_url": null,
+ "artifacts_file": null,
"finished_at": null,
"id": 69,
"name": "rubocop",
@@ -338,3 +376,53 @@ Example of response
"user": null
}
```
+
+## Erase a build
+
+Erase a single build of a project (remove build artifacts and a build trace)
+
+```
+POST /projects/:id/builds/:build_id/erase
+```
+
+Parameters
+
+| Attribute | Type | required | Description |
+|-------------|---------|----------|---------------------|
+| `id` | integer | yes | The ID of a project |
+| `build_id` | integer | yes | The ID of a build |
+
+Example of request
+
+```
+curl -X POST -H "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v3/projects/1/builds/1/erase"
+```
+
+Example of response
+
+```json
+{
+ "commit": {
+ "author_email": "admin@example.com",
+ "author_name": "Administrator",
+ "created_at": "2015-12-24T16:51:14.000+01:00",
+ "id": "0ff3ae198f8601a285adcf5c0fff204ee6fba5fd",
+ "message": "Test the CI integration.",
+ "short_id": "0ff3ae19",
+ "title": "Test the CI integration."
+ },
+ "coverage": null,
+ "download_url": null,
+ "id": 69,
+ "name": "rubocop",
+ "ref": "master",
+ "runner": null,
+ "stage": "test",
+ "created_at": "2016-01-11T10:13:33.506Z",
+ "started_at": "2016-01-11T10:13:33.506Z",
+ "finished_at": "2016-01-11T10:15:10.506Z",
+ "status": "failed",
+ "tag": false,
+ "user": null
+}
+```
diff --git a/doc/api/merge_requests.md b/doc/api/merge_requests.md
index 009532b50c1..5c527d55481 100644
--- a/doc/api/merge_requests.md
+++ b/doc/api/merge_requests.md
@@ -515,7 +515,7 @@ Parameters:
}
```
-## Comments on merge requets
+## Comments on merge requests
Comments are done via the [notes](notes.md) resource.
diff --git a/doc/api/runners.md b/doc/api/runners.md
new file mode 100644
index 00000000000..cc6c6b7cb2f
--- /dev/null
+++ b/doc/api/runners.md
@@ -0,0 +1,322 @@
+# Runners API
+
+> [Introduced][ce-2640] in GitLab 8.5
+
+[ce-2640]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/2640
+
+## List owned runners
+
+Get a list of specific runners available to the user.
+
+```
+GET /runners
+GET /runners?scope=active
+```
+
+| Attribute | Type | Required | Description |
+|-----------|---------|----------|---------------------|
+| `scope` | string | no | The scope of specific runners to show, one of: `active`, `paused`, `online`; showing all runners if none provided |
+
+```
+curl -H "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v3/runners"
+```
+
+Example response:
+
+```json
+[
+ {
+ "active": true,
+ "description": "test-1-20150125",
+ "id": 6,
+ "is_shared": false,
+ "name": null
+ },
+ {
+ "active": true,
+ "description": "test-2-20150125",
+ "id": 8,
+ "is_shared": false,
+ "name": null
+ }
+]
+```
+
+## List all runners
+
+Get a list of all runners in the GitLab instance (specific and shared). Access
+is restricted to users with `admin` privileges.
+
+```
+GET /runners/all
+GET /runners/all?scope=online
+```
+
+| Attribute | Type | Required | Description |
+|-----------|---------|----------|---------------------|
+| `scope` | string | no | The scope of runners to show, one of: `specific`, `shared`, `active`, `paused`, `online`; showing all runners if none provided |
+
+```
+curl -H "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v3/runners/all"
+```
+
+Example response:
+
+```json
+[
+ {
+ "active": true,
+ "description": "shared-runner-1",
+ "id": 1,
+ "is_shared": true,
+ "name": null
+ },
+ {
+ "active": true,
+ "description": "shared-runner-2",
+ "id": 3,
+ "is_shared": true,
+ "name": null
+ },
+ {
+ "active": true,
+ "description": "test-1-20150125",
+ "id": 6,
+ "is_shared": false,
+ "name": null
+ },
+ {
+ "active": true,
+ "description": "test-2-20150125",
+ "id": 8,
+ "is_shared": false,
+ "name": null
+ }
+]
+```
+
+## Get runner's details
+
+Get details of a runner.
+
+```
+GET /runners/:id
+```
+
+| Attribute | Type | Required | Description |
+|-----------|---------|----------|---------------------|
+| `id` | integer | yes | The ID of a runner |
+
+```
+curl -H "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v3/runners/6"
+```
+
+Example response:
+
+```json
+{
+ "active": true,
+ "architecture": null,
+ "description": "test-1-20150125",
+ "id": 6,
+ "is_shared": false,
+ "contacted_at": "2016-01-25T16:39:48.066Z",
+ "name": null,
+ "platform": null,
+ "projects": [
+ {
+ "id": 1,
+ "name": "GitLab Community Edition",
+ "name_with_namespace": "GitLab.org / GitLab Community Edition",
+ "path": "gitlab-ce",
+ "path_with_namespace": "gitlab-org/gitlab-ce"
+ }
+ ],
+ "token": "205086a8e3b9a2b818ffac9b89d102",
+ "revision": null,
+ "tag_list": [
+ "ruby",
+ "mysql"
+ ],
+ "version": null
+}
+```
+
+## Update runner's details
+
+Update details of a runner.
+
+```
+PUT /runners/:id
+```
+
+| Attribute | Type | Required | Description |
+|---------------|---------|----------|---------------------|
+| `id` | integer | yes | The ID of a runner |
+| `description` | string | no | The description of a runner |
+| `active` | boolean | no | The state of a runner; can be set to `true` or `false` |
+| `tag_list` | array | no | The list of tags for a runner; put array of tags, that should be finally assigned to a runner |
+
+```
+curl -X PUT -H "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v3/runners/6" -F "description=test-1-20150125-test" -F "tag_list=ruby,mysql,tag1,tag2"
+```
+
+Example response:
+
+```json
+{
+ "active": true,
+ "architecture": null,
+ "description": "test-1-20150125-test",
+ "id": 6,
+ "is_shared": false,
+ "contacted_at": "2016-01-25T16:39:48.066Z",
+ "name": null,
+ "platform": null,
+ "projects": [
+ {
+ "id": 1,
+ "name": "GitLab Community Edition",
+ "name_with_namespace": "GitLab.org / GitLab Community Edition",
+ "path": "gitlab-ce",
+ "path_with_namespace": "gitlab-org/gitlab-ce"
+ }
+ ],
+ "token": "205086a8e3b9a2b818ffac9b89d102",
+ "revision": null,
+ "tag_list": [
+ "ruby",
+ "mysql",
+ "tag1",
+ "tag2"
+ ],
+ "version": null
+}
+```
+
+## Remove a runner
+
+Remove a runner.
+
+```
+DELETE /runners/:id
+```
+
+| Attribute | Type | Required | Description |
+|-----------|---------|----------|---------------------|
+| `id` | integer | yes | The ID of a runner |
+
+```
+curl -X DELETE -H "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v3/runners/6"
+```
+
+Example response:
+
+```json
+{
+ "active": true,
+ "description": "test-1-20150125-test",
+ "id": 6,
+ "is_shared": false,
+ "name": null,
+}
+```
+
+## List project's runners
+
+List all runners (specific and shared) available in the project. Shared runners
+are listed if at least one shared runner is defined **and** shared runners
+usage is enabled in the project's settings.
+
+```
+GET /projects/:id/runners
+```
+
+| Attribute | Type | Required | Description |
+|-----------|---------|----------|---------------------|
+| `id` | integer | yes | The ID of a project |
+
+```
+curl -H "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v3/projects/9/runners"
+```
+
+Example response:
+
+```json
+[
+ {
+ "active": true,
+ "description": "test-2-20150125",
+ "id": 8,
+ "is_shared": false,
+ "name": null
+ },
+ {
+ "active": true,
+ "description": "development_runner",
+ "id": 5,
+ "is_shared": true,
+ "name": null
+ }
+]
+```
+
+## Enable a runner in project
+
+Enable an available specific runner in the project.
+
+```
+POST /projects/:id/runners
+```
+
+| Attribute | Type | Required | Description |
+|-------------|---------|----------|---------------------|
+| `id` | integer | yes | The ID of a project |
+| `runner_id` | integer | yes | The ID of a runner |
+
+```
+curl -X POST -H "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v3/project/9/runners" -F "runner_id=9"
+```
+
+Example response:
+
+```json
+{
+ "active": true,
+ "description": "test-2016-02-01",
+ "id": 9,
+ "is_shared": false,
+ "name": null
+}
+```
+
+## Disable a runner from project
+
+Disable a specific runner from the project. It works only if the project isn't
+the only project associated with the specified runner. If so, an error is
+returned. Use the [Remove a runner](#remove-a-runner) call instead.
+
+```
+DELETE /projects/:id/runners/:runner_id
+```
+
+| Attribute | Type | Required | Description |
+|-------------|---------|----------|---------------------|
+| `id` | integer | yes | The ID of a project |
+| `runner_id` | integer | yes | The ID of a runner |
+
+```
+curl -X DELETE -H "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v3/project/9/runners/9"
+```
+
+Example response:
+
+```json
+{
+ "active": true,
+ "description": "test-2016-02-01",
+ "id": 9,
+ "is_shared": false,
+ "name": null
+}
+```
diff --git a/doc/ci/README.md b/doc/ci/README.md
index 5886829be51..2120b5b2850 100644
--- a/doc/ci/README.md
+++ b/doc/ci/README.md
@@ -1,39 +1,37 @@
## GitLab CI Documentation
-### User documentation
-
-* [Quick Start](quick_start/README.md)
-* [Enable or disable GitLab CI](enable_or_disable_ci.md)
-* [Configuring project (.gitlab-ci.yml)](yaml/README.md)
-* [Configuring runner](runners/README.md)
-* [Configuring deployment](deployment/README.md)
-* [Using Docker Images](docker/using_docker_images.md)
-* [Using Docker Build](docker/using_docker_build.md)
-* [Using Variables](variables/README.md)
-* [Using SSH keys](ssh_keys/README.md)
-* [Triggering builds through the API](triggers/README.md)
-* [Build artifacts](build_artifacts/README.md)
-
-### Languages
-
-* [Testing PHP](languages/php.md)
-
-### Services
-
-* [Using MySQL](services/mysql.md)
-* [Using PostgreSQL](services/postgres.md)
-* [Using Redis](services/redis.md)
-* [Using Other Services](docker/using_docker_images.md#how-to-use-other-images-as-services)
-
-### Examples
-
-+ [The .gitlab-ci.yml file for GitLab itself](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/.gitlab-ci.yml)
-+ [Test and deploy Ruby applications to Heroku](examples/test-and-deploy-ruby-application-to-heroku.md)
-+ [Test and deploy Python applications to Heroku](examples/test-and-deploy-python-application-to-heroku.md)
-+ [Test Clojure applications](examples/test-clojure-application.md)
-+ Help your favorite programming language and GitLab by sending a merge request with a guide for that language.
-
-### Administrator documentation
-
-* [User permissions](permissions/README.md)
-* [API](api/README.md)
+### CI User documentation
+
+- [Get started with GitLab CI](quick_start/README.md)
+- [Learn how to enable or disable GitLab CI](enable_or_disable_ci.md)
+- [Learn how `.gitlab-ci.yml` works](yaml/README.md)
+- [Configure a Runner, the application that runs your builds](runners/README.md)
+- [Use Docker images with GitLab Runner](docker/using_docker_images.md)
+- [Use CI to build Docker images](docker/using_docker_build.md)
+- [Use variables in your `.gitlab-ci.yml`](variables/README.md)
+- [Use SSH keys in your build environment](ssh_keys/README.md)
+- [Trigger builds through the API](triggers/README.md)
+- [Build artifacts](build_artifacts/README.md)
+- [User permissions](permissions/README.md)
+- [API](api/README.md)
+
+### CI Examples
+
+- [The .gitlab-ci.yml file for GitLab itself](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/.gitlab-ci.yml)
+- [Test your PHP applications](examples/php.md)
+- [Test and deploy Ruby applications to Heroku](examples/test-and-deploy-ruby-application-to-heroku.md)
+- [Test and deploy Python applications to Heroku](examples/test-and-deploy-python-application-to-heroku.md)
+- [Test Clojure applications](examples/test-clojure-application.md)
+- [Using `dpl` as deployment tool](deployment/README.md)
+- Help your favorite programming language and GitLab by sending a merge request
+ with a guide for that language.
+
+### CI Services
+
+GitLab CI uses the `services` keyword to define what docker containers should
+be linked with your base image. Below is a list of examples you may use:
+
+- [Using MySQL](services/mysql.md)
+- [Using PostgreSQL](services/postgres.md)
+- [Using Redis](services/redis.md)
+- [Using Other Services](docker/using_docker_images.md#how-to-use-other-images-as-services)
diff --git a/doc/ci/api/runners.md b/doc/ci/api/runners.md
index c383dc4bcc9..e9033aeacd5 100644
--- a/doc/ci/api/runners.md
+++ b/doc/ci/api/runners.md
@@ -1,5 +1,9 @@
# Runners API
+_**Note:** This API is intended to be used only by Runners as their own
+communication channel. For the consumer API see the
+[new Runners API](../../api/runners.md)._
+
## Runners
### Retrieve all runners
@@ -74,4 +78,4 @@ Returns:
"updated_at" : "2015-02-26T11:39:39.232Z",
"description" : "awesome runner"
}
-``` \ No newline at end of file
+```
diff --git a/doc/ci/examples/README.md b/doc/ci/examples/README.md
index 1cf41aea391..31f29f4a082 100644
--- a/doc/ci/examples/README.md
+++ b/doc/ci/examples/README.md
@@ -1,5 +1,13 @@
-# Build script examples
+## Build script examples
-+ [Test and deploy a Ruby application to Heroku](test-and-deploy-ruby-application-to-heroku.md)
-+ [Test and deploy a Python application to Heroku](test-and-deploy-python-application-to-heroku.md)
-+ [Test a Clojure application](test-clojure-application.md)
+- [Test and deploy a Ruby application to Heroku](test-and-deploy-ruby-application-to-heroku.md)
+- [Test and deploy a Python application to Heroku](test-and-deploy-python-application-to-heroku.md)
+- [Test a Clojure application](test-clojure-application.md)
+
+## Languages
+
+This is a list of languages you can test with GitLab CI. Each section has
+comprehensive documentation and comes with a test repository hosted on
+GitLab.com.
+
+- [Testing PHP](php.md)
diff --git a/doc/ci/languages/php.md b/doc/ci/examples/php.md
index aeadd6a448e..aeadd6a448e 100644
--- a/doc/ci/languages/php.md
+++ b/doc/ci/examples/php.md
diff --git a/doc/ci/languages/README.md b/doc/ci/languages/README.md
deleted file mode 100644
index 54b2343e08b..00000000000
--- a/doc/ci/languages/README.md
+++ /dev/null
@@ -1,7 +0,0 @@
-### Languages
-
-This is a list of languages you can test with GitLab CI. Each section has
-comprehensive documentation and comes with a test repository hosted on
-GitLab.com
-
-+ [Testing PHP](php.md)
diff --git a/doc/ci/quick_start/README.md b/doc/ci/quick_start/README.md
index ae7b760fa67..624d9899c79 100644
--- a/doc/ci/quick_start/README.md
+++ b/doc/ci/quick_start/README.md
@@ -1,25 +1,47 @@
# Quick Start
-Starting from version 8.0, GitLab Continuous Integration (CI) is fully
-integrated into GitLab itself and is enabled by default on all projects.
+>**Note:** Starting from version 8.0, GitLab [Continuous Integration][ci] (CI)
+is fully integrated into GitLab itself and is [enabled] by default on all
+projects.
-This guide assumes that you:
+The TL;DR version of how GitLab CI works is the following.
-- have a working GitLab instance of version 8.0 or higher or are using
- [GitLab.com](https://gitlab.com/users/sign_in)
-- have a project in GitLab that you would like to use CI for
+---
+
+GitLab offers a [continuous integration][ci] service. If you
+[add a `.gitlab-ci.yml` file][yaml] to the root directory of your repository,
+and configure your GitLab project to use a [Runner], then each merge request or
+push triggers a build.
-In brief, the steps needed to have a working CI can be summed up to:
+The `.gitlab-ci.yml` file tells the GitLab runner what do to. By default it
+runs three [stages]: `build`, `test`, and `deploy`.
-1. Create a new project
-1. Add `.gitlab-ci.yml` to the git repository and push to GitLab
+If everything runs OK (no non-zero return values), you'll get a nice green
+checkmark associated with the pushed commit or merge request. This makes it
+easy to see whether a merge request will cause any of the tests to fail before
+you even look at the code.
+
+Most projects only use GitLab's CI service to run the test suite so that
+developers get immediate feedback if they broke something.
+
+So in brief, the steps needed to have a working CI can be summed up to:
+
+1. Add `.gitlab-ci.yml` to the root directory of your repository
1. Configure a Runner
-From there on, on every push to your git repository the build will be
+From there on, on every push to your Git repository, the build will be
automagically started by the Runner and will appear under the project's
`/builds` page.
-Now, let's break it down to pieces and work on solving the GitLab CI puzzle.
+---
+
+This guide assumes that you:
+
+- have a working GitLab instance of version 8.0 or higher or are using
+ [GitLab.com](https://gitlab.com/users/sign_in)
+- have a project in GitLab that you would like to use CI for
+
+Let's break it down to pieces and work on solving the GitLab CI puzzle.
## Creating a `.gitlab-ci.yml` file
@@ -36,13 +58,13 @@ file and start builds on _Runners_ according to the contents of the file,
for that commit.
Because `.gitlab-ci.yml` is in the repository, it is version controlled,
-old versions still build succesfully, forks can easily make use of CI,
+old versions still build successfully, forks can easily make use of CI,
branches can have separate builds and you have a single source of truth for CI.
You can read more about the reasons why we are using `.gitlab-ci.yml`
[in our blog about it][blog-ci].
**Note:** `.gitlab-ci.yml` is a [YAML](https://en.wikipedia.org/wiki/YAML) file
-so you have to pay extra attention to the identation. Always use spaces, not
+so you have to pay extra attention to the indentation. Always use spaces, not
tabs.
### Creating a simple `.gitlab-ci.yml` file
@@ -124,7 +146,7 @@ In GitLab, Runners run the builds that you define in `.gitlab-ci.yml`.
A Runner can be a virtual machine, a VPS, a bare-metal machine, a docker
container or even a cluster of containers. GitLab and the Runners communicate
through an API, so the only needed requirement is that the machine on which the
-Runner is configured to has Internet access.
+Runner is configured to have Internet access.
A Runner can be specific to a certain project or serve multiple projects in
GitLab. If it serves all projects it's called a _Shared Runner_.
@@ -168,7 +190,7 @@ To enable **Shared Runners** you have to go to your project's
## Seeing the status of your build
-After configuring the Runner succesfully, you should see the status of your
+After configuring the Runner successfully, you should see the status of your
last commit change from _pending_ to either _running_, _success_ or _failed_.
You can view all builds, by going to the **Builds** page in your project.
@@ -184,6 +206,15 @@ you expected.
You are also able to view the status of any commit in the various pages in
GitLab, such as **Commits** and **Merge Requests**.
+## Enabling build emails
+
+If you want to receive e-mail notifications about the result status of the
+builds, you should explicitly enable the **Builds Emails** service under your
+project's settings.
+
+For more information read the [Builds emails service documentation]
+(../../project_services/builds_emails.md).
+
## Builds badge
You can access a builds badge image using following link:
@@ -192,6 +223,11 @@ You can access a builds badge image using following link:
http://example.gitlab.com/namespace/project/badges/branch/build.svg
```
+## Examples
+
+Visit the [examples README][examples] to see a list of examples using GitLab
+CI with various languages.
+
## Next steps
Awesome! You started using CI in GitLab!
@@ -203,3 +239,9 @@ Visit our various languages examples at <https://gitlab.com/groups/gitlab-exampl
[runner-install]: https://gitlab.com/gitlab-org/gitlab-ci-multi-runner/tree/master#installation
[blog-ci]: https://about.gitlab.com/2015/05/06/why-were-replacing-gitlab-ci-jobs-with-gitlab-ci-dot-yml/
+[examples]: ../examples/README.md
+[ci]: https://about.gitlab.com/gitlab-ci/
+[yaml]: ../yaml/README.md
+[runner]: ../runners/README.md
+[enabled]: ../enable_or_disable_ci.md
+[stages]: ../yaml/README.md#stages
diff --git a/doc/ci/services/README.md b/doc/ci/services/README.md
index 1ebb0a4a250..4b79461d55c 100644
--- a/doc/ci/services/README.md
+++ b/doc/ci/services/README.md
@@ -1,9 +1,9 @@
## GitLab CI Services
-GitLab CI uses the `services` keyword to define what docker containers should be
-linked with your base image. Below is a list of examples you may use.
+GitLab CI uses the `services` keyword to define what docker containers should
+be linked with your base image. Below is a list of examples you may use.
-+ [Using MySQL](mysql.md)
-+ [Using PostgreSQL](postgres.md)
-+ [Using Redis](redis.md)
-+ [Using Other Services](../docker/using_docker_images.md#how-to-use-other-images-as-services)
+- [Using MySQL](mysql.md)
+- [Using PostgreSQL](postgres.md)
+- [Using Redis](redis.md)
+- [Using Other Services](../docker/using_docker_images.md#how-to-use-other-images-as-services)
diff --git a/doc/ci/variables/README.md b/doc/ci/variables/README.md
index 018d1898594..b0e53cbc261 100644
--- a/doc/ci/variables/README.md
+++ b/doc/ci/variables/README.md
@@ -16,7 +16,7 @@ The API_TOKEN will take the Secure Variable value: `SECURE`.
### Predefined variables (Environment Variables)
| Variable | Runner | Description |
-|-------------------------|-------------|
+|-------------------------|-----|--------|
| **CI** | 0.4 | Mark that build is executed in CI environment |
| **GITLAB_CI** | all | Mark that build is executed in GitLab CI environment |
| **CI_SERVER** | all | Mark that build is executed in CI environment |
@@ -30,7 +30,7 @@ The API_TOKEN will take the Secure Variable value: `SECURE`.
| **CI_BUILD_REF_NAME** | all | The branch or tag name for which project is built |
| **CI_BUILD_ID** | all | The unique id of the current build that GitLab CI uses internally |
| **CI_BUILD_REPO** | all | The URL to clone the Git repository |
-| **CI_BUILD_TRIGGERED** | 0.5 | The flag to indicate that build was triggered |
+| **CI_BUILD_TRIGGERED** | 0.5 | The flag to indicate that build was [triggered] |
| **CI_PROJECT_ID** | all | The unique id of the current project that GitLab CI uses internally |
| **CI_PROJECT_DIR** | all | The full path where the repository is cloned and where the build is ran |
@@ -104,3 +104,5 @@ job_name:
script:
- export
```
+
+[triggered]: ../triggers/README.md
diff --git a/doc/ci/yaml/README.md b/doc/ci/yaml/README.md
index 194c8171bb9..051eaa04152 100644
--- a/doc/ci/yaml/README.md
+++ b/doc/ci/yaml/README.md
@@ -33,7 +33,7 @@ The YAML syntax allows for using more complex job specifications than in the
above example:
```yaml
-image: ruby:2.2
+image: ruby:2.1
services:
- postgres
@@ -428,8 +428,30 @@ artifacts:
- binaries/
```
-The artifacts will be send after a successful build success to GitLab, and will
-be accessible in the GitLab UI to download.
+You may want to create artifacts only for tagged releases to avoid filling the
+build server storage with temporary build artifacts.
+
+Create artifacts only for tags (`default-job` will not create artifacts):
+
+```yaml
+default-job:
+ script:
+ - mvn test -U
+ except:
+ - tags
+
+release-job:
+ script:
+ - mvn package -U
+ artifacts:
+ paths:
+ - target/*.war
+ only:
+ - tags
+```
+
+The artifacts will be sent to GitLab after a successful build and will
+be available for download in the GitLab UI.
### cache
@@ -496,3 +518,10 @@ You can find the link under `/ci/lint` of your gitlab instance.
If your commit message contains `[ci skip]`, the commit will be created but the
builds will be skipped.
+
+## Examples
+
+Visit the [examples README][examples] to see a list of examples using GitLab
+CI with various languages.
+
+[examples]: ../examples/README.md
diff --git a/doc/customization/branded_login_page.md b/doc/customization/branded_login_page.md
new file mode 100644
index 00000000000..d4d9f5f7b5e
--- /dev/null
+++ b/doc/customization/branded_login_page.md
@@ -0,0 +1,19 @@
+# Changing the appearance of the login page
+
+GitLab Community Edition offers a way to put your company's identity on the login page of your GitLab server and make it a branded login page.
+
+By default, the page shows the GitLab logo and description.
+
+![default_login_page](branded_login_page/default_login_page.png)
+
+## Changing the appearance of the login page
+
+Navigate to the **Admin** area and go to the **Appearance** page.
+
+Fill in the required details like Title, Description and upload the company logo.
+
+![appearance](branded_login_page/appearance.png)
+
+After saving the page, your GitLab login page will have the details you filled in:
+
+![company_login_page](branded_login_page/custom_sign_in.png)
diff --git a/doc/customization/branded_login_page/appearance.png b/doc/customization/branded_login_page/appearance.png
new file mode 100644
index 00000000000..6bce1f0a287
--- /dev/null
+++ b/doc/customization/branded_login_page/appearance.png
Binary files differ
diff --git a/doc/customization/branded_login_page/custom_sign_in.png b/doc/customization/branded_login_page/custom_sign_in.png
new file mode 100644
index 00000000000..d6020b029a2
--- /dev/null
+++ b/doc/customization/branded_login_page/custom_sign_in.png
Binary files differ
diff --git a/doc/customization/branded_login_page/default_login_page.png b/doc/customization/branded_login_page/default_login_page.png
new file mode 100644
index 00000000000..795c7954d8e
--- /dev/null
+++ b/doc/customization/branded_login_page/default_login_page.png
Binary files differ
diff --git a/doc/customization/welcome_message.md b/doc/customization/welcome_message.md
index e993230bb88..a0cb234bea0 100644
--- a/doc/customization/welcome_message.md
+++ b/doc/customization/welcome_message.md
@@ -1,12 +1,12 @@
-# Customize the complete sign-in page (GitLab Enterprise Edition only)
+# Customize the complete sign-in page
-Please see [Branded login page](http://doc.gitlab.com/ee/customization/branded_login_page.html)
+Please see [Branded login page](branded_login_page.md)
# Add a welcome message to the sign-in page (GitLab Community Edition)
It is possible to add a markdown-formatted welcome message to your GitLab
sign-in page. Users of GitLab Enterprise Edition should use the [branded login
-page feature](/ee/customization/branded_login_page.html) instead.
+page feature](branded_login_page.md) instead.
The welcome message (extra_sign_in_text) can now be set/changed in the Admin UI.
-Admin area > Settings \ No newline at end of file
+Admin area > Settings
diff --git a/doc/development/README.md b/doc/development/README.md
index d5bf166ad32..b9a0d81e5ba 100644
--- a/doc/development/README.md
+++ b/doc/development/README.md
@@ -1,11 +1,12 @@
# Development
- [Architecture](architecture.md) of GitLab
-- [Shell commands](shell_commands.md) in the GitLab codebase
-- [Rake tasks](rake_tasks.md) for development
+- [Benchmarking](benchmarking.md)
- [CI setup](ci_setup.md) for testing GitLab
+- [Gotchas](gotchas.md) to avoid
+- [How to dump production data to staging](db_dump.md)
+- [Migration Style Guide](migration_style_guide.md) for creating safe migrations
+- [Rake tasks](rake_tasks.md) for development
+- [Shell commands](shell_commands.md) in the GitLab codebase
- [Sidekiq debugging](sidekiq_debugging.md)
- [UI guide](ui_guide.md) for building GitLab with existing css styles and elements
-- [Migration Style Guide](migration_style_guide.md) for creating safe migrations
-- [How to dump production data to staging](dump_db.md)
-- [Benchmarking](benchmarking.md)
diff --git a/doc/development/architecture.md b/doc/development/architecture.md
index 6101a71a8de..12e33406cb6 100644
--- a/doc/development/architecture.md
+++ b/doc/development/architecture.md
@@ -42,7 +42,7 @@ Gitlab-shell communicates with Sidekiq via the “communication board” (Redis)
## System Layout
-When referring to ~git in the pictures it means the home directory of the git user which is typically /home/git.
+When referring to `~git` in the pictures it means the home directory of the git user which is typically /home/git.
GitLab is primarily installed within the `/home/git` user home directory as `git` user. Within the home directory is where the gitlabhq server software resides as well as the repositories (though the repository location is configurable).
diff --git a/doc/development/ci_setup.md b/doc/development/ci_setup.md
index 05db30b4a7e..6776d9b083f 100644
--- a/doc/development/ci_setup.md
+++ b/doc/development/ci_setup.md
@@ -26,7 +26,7 @@ We use [these build scripts](https://gitlab.com/gitlab-org/gitlab-ci/blob/master
# Build configuration on [Semaphore](https://semaphoreapp.com/gitlabhq/gitlabhq/) for testing the [GitHub.com repo](https://github.com/gitlabhq/gitlabhq)
- Language: Ruby
-- Ruby version: 2.2.4
+- Ruby version: 2.1.8
- database.yml: pg
Build commands
diff --git a/doc/development/doc_styleguide.md b/doc/development/doc_styleguide.md
index 96d1dffbc52..187ec9e7b75 100644
--- a/doc/development/doc_styleguide.md
+++ b/doc/development/doc_styleguide.md
@@ -85,9 +85,19 @@ Inside the document:
## Notes
-- Notes should be in italics with the word `Note:` being bold. Use this form:
- `_**Note:** This is something to note._`. If the note spans across multiple
- lines it's OK to split the line.
+- Notes should be quoted with the word `Note:` being bold. Use this form:
+
+ ```
+ >**Note:**
+ This is something to note.
+ ```
+
+ which renders to:
+
+ >**Note:**
+ This is something to note.
+
+ If the note spans across multiple lines it's OK to split the line.
## New features
diff --git a/doc/development/gotchas.md b/doc/development/gotchas.md
new file mode 100644
index 00000000000..21078c8d6f9
--- /dev/null
+++ b/doc/development/gotchas.md
@@ -0,0 +1,103 @@
+# Gotchas
+
+The purpose of this guide is to document potential "gotchas" that contributors
+might encounter or should avoid during development of GitLab CE and EE.
+
+## Don't `describe` symbols
+
+Consider the following model spec:
+
+```ruby
+require 'rails_helper'
+
+describe User do
+ describe :to_param do
+ it 'converts the username to a param' do
+ user = described_class.new(username: 'John Smith')
+
+ expect(user.to_param).to eq 'john-smith'
+ end
+ end
+end
+```
+
+When run, this spec doesn't do what we might expect:
+
+```sh
+spec/models/user_spec.rb|6 error| Failure/Error: u = described_class.new NoMethodError: undefined method `new' for :to_param:Symbol
+```
+
+### Solution
+
+Except for the top-level `describe` block, always provide a String argument to
+`describe`.
+
+## Don't `rescue Exception`
+
+See ["Why is it bad style to `rescue Exception => e` in Ruby?"][Exception].
+
+_**Note:** This rule is [enforced automatically by
+Rubocop](https://gitlab.com/gitlab-org/gitlab-ce/blob/8-4-stable/.rubocop.yml#L911-914)._
+
+[Exception]: http://stackoverflow.com/q/10048173/223897
+
+## Don't use inline CoffeeScript in views
+
+Using the inline `:coffee` or `:coffeescript` Haml filters comes with a
+performance overhead.
+
+_**Note:** We've [removed these two filters](https://gitlab.com/gitlab-org/gitlab-ce/blob/8-5-stable/config/initializers/haml.rb)
+in an initializer._
+
+### Further reading
+
+- Pull Request: [Replace CoffeeScript block into JavaScript in Views](https://git.io/vztMu)
+- Stack Overflow: [Performance implications of using :coffescript filter inside HAML templates?](http://stackoverflow.com/a/17571242/223897)
+
+## ID-based CSS selectors need to be a bit more specific
+
+Normally, because HTML `id` attributes need to be unique to the page, it's
+perfectly fine to write some JavaScript like the following:
+
+```javascript
+$('#js-my-selector').hide();
+```
+
+However, there's a feature of GitLab's Markdown processing that [automatically
+adds anchors to header elements][ToC Processing], with the `id` attribute being
+automatically generated based on the content of the header.
+
+Unfortunately, this feature makes it possible for user-generated content to
+create a header element with the same `id` attribute we're using in our
+selector, potentially breaking the JavaScript behavior. A user could break the
+above example with the following Markdown:
+
+```markdown
+## JS My Selector
+```
+
+Which gets converted to the following HTML:
+
+```html
+<h2>
+ <a id="js-my-selector" class="anchor" href="#js-my-selector" aria-hidden="true"></a>
+ JS My Selector
+</h2>
+```
+
+[ToC Processing]: https://gitlab.com/gitlab-org/gitlab-ce/blob/8-4-stable/lib/banzai/filter/table_of_contents_filter.rb#L31-37
+
+### Solution
+
+The current recommended fix for this is to make our selectors slightly more
+specific:
+
+```javascript
+$('div#js-my-selector').hide();
+```
+
+### Further reading
+
+- Issue: [Merge request ToC anchor conflicts with tabs](https://gitlab.com/gitlab-org/gitlab-ce/issues/3908)
+- Merge Request: [Make tab target selectors less naive](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/2023)
+- Merge Request: [Make cross-project reference's clipboard target less naive](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/2024)
diff --git a/doc/development/migration_style_guide.md b/doc/development/migration_style_guide.md
index 4fa1961fde9..28dedf3978c 100644
--- a/doc/development/migration_style_guide.md
+++ b/doc/development/migration_style_guide.md
@@ -8,12 +8,14 @@ In addition, having to take a server offline for a an upgrade small or big is
a big burden for most organizations. For this reason it is important that your
migrations are written carefully, can be applied online and adhere to the style guide below.
+It's advised to have offline migrations only in major GitLab releases.
+
When writing your migrations, also consider that databases might have stale data
or inconsistencies and guard for that. Try to make as little assumptions as possible
about the state of the database.
Please don't depend on GitLab specific code since it can change in future versions.
-If needed copy-paste GitLab code into the migration to make make it forward compatible.
+If needed copy-paste GitLab code into the migration to make it forward compatible.
## Comments in the migration
@@ -33,6 +35,8 @@ It is always preferable to have a migration run online. If you expect the migrat
to take particularly long (for instance, if it loops through all notes),
this is valuable information to add.
+If you don't provide the information it means that a migration is safe to run online.
+
### Reversibility
Your migration should be reversible. This is very important, as it should
@@ -85,4 +89,4 @@ select_all("SELECT name, COUNT(id) as cnt FROM tags GROUP BY name HAVING COUNT(i
execute("UPDATE taggings SET tag_id = #{origin_tag_id} WHERE tag_id IN(#{duplicate_ids.join(",")})")
execute("DELETE FROM tags WHERE id IN(#{duplicate_ids.join(",")})")
end
-```
+``` \ No newline at end of file
diff --git a/doc/gitlab-basics/basicsimages/compare_braches.png b/doc/gitlab-basics/basicsimages/compare_branches.png
index 7eebaed9075..7eebaed9075 100644
--- a/doc/gitlab-basics/basicsimages/compare_braches.png
+++ b/doc/gitlab-basics/basicsimages/compare_branches.png
Binary files differ
diff --git a/doc/install/installation.md b/doc/install/installation.md
index 7d3f9d0a2ed..0fd54be58b0 100644
--- a/doc/install/installation.md
+++ b/doc/install/installation.md
@@ -107,7 +107,7 @@ Then select 'Internet Site' and press enter to confirm the hostname.
## 2. Ruby
-_**Note:** The current supported Ruby versions are 2.1.x and 2.2.x. Ruby 2.3 is
+_**Note:** The current supported Ruby version is 2.1.x. Ruby 2.2 and 2.3 are
currently not supported._
The use of Ruby version managers such as [RVM], [rbenv] or [chruby] with GitLab
@@ -123,9 +123,9 @@ Remove the old Ruby 1.8 if present:
Download Ruby and compile it:
mkdir /tmp/ruby && cd /tmp/ruby
- curl -O --progress https://cache.ruby-lang.org/pub/ruby/2.2/ruby-2.2.4.tar.gz
- echo 'b6eff568b48e0fda76e5a36333175df049b204e91217aa32a65153cc0cdcb761 ruby-2.2.4.tar.gz' | sha256sum -c - && tar xzf ruby-2.2.4.tar.gz
- cd ruby-2.2.4
+ curl -O --progress https://cache.ruby-lang.org/pub/ruby/2.1/ruby-2.1.8.tar.gz
+ echo 'c7e50159357afd87b13dc5eaf4ac486a70011149 ruby-2.1.8.tar.gz' | shasum -c - && tar xzf ruby-2.1.8.tar.gz
+ cd ruby-2.1.8
./configure --disable-install-rdoc
make
sudo make install
@@ -182,25 +182,20 @@ We recommend using a PostgreSQL database. For MySQL check [MySQL setup guide](da
## 6. Redis
-As of this writing, most Debian/Ubuntu distributions ship with Redis 2.2 or
-2.4. GitLab requires at least Redis 2.8.
+GitLab requires at least Redis 2.8.
-Ubuntu users [can use a PPA](https://launchpad.net/~chris-lea/+archive/ubuntu/redis-server)
-to install a recent version of Redis.
-
-The following instructions cover building and installing Redis from scratch:
+If you are using Debian 8 or Ubuntu 14.04 and up, then you can simply install
+Redis 2.8 with:
```sh
-# Build Redis
-wget http://download.redis.io/releases/redis-2.8.23.tar.gz
-tar xzf redis-2.8.23.tar.gz
-cd redis-2.8.23
-make
+sudo apt-get install redis-server
+```
-# Install Redis
-cd utils
-sudo ./install_server.sh
+If you are using Debian 7 or Ubuntu 12.04, follow the special documentation
+on [an alternate Redis installation](redis.md). Once done, follow the rest of
+the guide here.
+```
# Configure redis to use sockets
sudo cp /etc/redis/redis.conf /etc/redis/redis.conf.orig
@@ -224,7 +219,7 @@ if [ -d /etc/tmpfiles.d ]; then
fi
# Activate the changes to redis.conf
-sudo service redis_6379 start
+sudo service redis-server restart
# Add git to the redis group
sudo usermod -aG redis git
@@ -270,8 +265,9 @@ sudo usermod -aG redis git
# Create the public/uploads/ directory
sudo -u git -H mkdir public/uploads/
- # Make sure GitLab can write to the public/uploads/ directory
- sudo chmod -R u+rwX public/uploads
+ # Make sure only the GitLab user has access to the public/uploads/ directory
+ # now that files in public/uploads are served by gitlab-workhorse
+ sudo chmod 0700 public/uploads
# Change the permissions of the directory where CI build traces are stored
sudo chmod -R u+rwX builds/
@@ -358,7 +354,7 @@ GitLab Shell is an SSH access and repository management software developed speci
cd /home/git
sudo -u git -H git clone https://gitlab.com/gitlab-org/gitlab-workhorse.git
cd gitlab-workhorse
- sudo -u git -H git checkout 0.6.4
+ sudo -u git -H git checkout 0.6.5
sudo -u git -H make
### Initialize Database and Activate Advanced Features
@@ -471,12 +467,15 @@ NOTE: Supply `SANITIZE=true` environment variable to `gitlab:check` to omit proj
### Initial Login
-Visit YOUR_SERVER in your web browser for your first GitLab login. The setup has created a default admin account for you. You can use it to log in:
+Visit YOUR_SERVER in your web browser for your first GitLab login.
- root
- 5iveL!fe
+If you didn't [provide a root password during setup](#initialize-database-and-activate-advanced-features),
+you'll be redirected to a password reset screen to provide the password for the
+initial administrator account. Enter your desired password and you'll be
+redirected back to the login screen.
-**Important Note:** On login you'll be prompted to change the password.
+The default account's username is **root**. Provide the password you created
+earlier and login. After login you can change the username if you wish.
**Enjoy!**
diff --git a/doc/install/redis.md b/doc/install/redis.md
new file mode 100644
index 00000000000..4075e6283d0
--- /dev/null
+++ b/doc/install/redis.md
@@ -0,0 +1,60 @@
+# Install Redis on old distributions
+
+GitLab requires at least Redis 2.8. The following guide is for Debian 7 and
+Ubuntu 12.04. If you are using Debian 8 or Ubuntu 14.04 and up, follow the
+[installation guide](installation.md).
+
+## Install Redis 2.8 in Debian 7
+
+Redis 2.8 is included in the Debian Wheezy [backports] repository.
+
+1. Edit `/etc/apt/sources.list` and add the following line:
+
+ ```
+ deb http://http.debian.net/debian wheezy-backports main
+ ```
+
+1. Update the repositories:
+
+ ```
+ sudo apt-get update
+ ```
+
+1. Install `redis-server`:
+
+ ```
+ sudo apt-get -t wheezy-backports install redis-server
+ ```
+
+1. Follow the rest of the [installation guide](installation.md).
+
+## Install Redis 2.8 in Ubuntu 12.04
+
+We will [use a PPA](https://launchpad.net/~chris-lea/+archive/ubuntu/redis-server)
+to install a recent version of Redis.
+
+1. Install the PPA repository:
+
+ ```
+ sudo add-apt-repository ppa:chris-lea/redis-server
+ ```
+
+ Your system will now fetch the PPA's key. This enables your Ubuntu system to
+ verify that the packages in the PPA have not been interfered with since they
+ were built.
+
+1. Update the repositories:
+
+ ```
+ sudo apt-get update
+ ```
+
+1. Install `redis-server`:
+
+ ```
+ sudo apt-get install redis-server
+ ```
+
+1. Follow the rest of the [installation guide](installation.md).
+
+[backports]: http://backports.debian.org/Instructions/ "Debian backports website"
diff --git a/doc/install/relative_url.md b/doc/install/relative_url.md
index b341b6311c6..0245febfcd8 100644
--- a/doc/install/relative_url.md
+++ b/doc/install/relative_url.md
@@ -25,7 +25,7 @@ that points to your GitLab instance.
The TL;DR list of configuration files that you need to change in order to
serve GitLab under a relative URL is:
-- `/home/git/gitlab/config/application.rb`
+- `/home/git/gitlab/config/initializers/relative_url.rb`
- `/home/git/gitlab/config/gitlab.yml`
- `/home/git/gitlab/config/unicorn.rb`
- `/home/git/gitlab-shell/config.yml`
@@ -66,8 +66,14 @@ Make sure to follow all steps below:
sudo service gitlab stop
```
-1. Edit `/home/git/gitlab/config/application.rb` and uncomment/change the
- following line:
+1. Create `/home/git/gitlab/config/initializers/relative_url.rb`
+
+ ```shell
+ cp /home/git/gitlab/config/initializers/relative_url.rb.sample \
+ /home/git/gitlab/config/initializers/relative_url.rb
+ ```
+
+ and change the following line:
```ruby
config.relative_url_root = "/gitlab"
@@ -119,8 +125,12 @@ Make sure to follow all steps below:
### Disable relative URL in GitLab
-To disable the relative URL, follow the same steps as above and set up the
-GitLab URL to one that doesn't contain a relative path.
+To disable the relative URL:
+
+1. Remove `/home/git/gitlab/config/initializers/relative_url.rb`
+
+1. Follow the same as above starting from 2. and set up the
+ GitLab URL to one that doesn't contain a relative path.
[omnibus-rel]: http://doc.gitlab.com/omnibus/settings/configuration.html#configuring-a-relative-url-for-gitlab "How to setup relative URL in Omnibus GitLab"
[restart gitlab]: ../administration/restart_gitlab.md#installations-from-source "How to restart GitLab"
diff --git a/doc/install/requirements.md b/doc/install/requirements.md
index c6a1c20d02f..8df142c531b 100644
--- a/doc/install/requirements.md
+++ b/doc/install/requirements.md
@@ -32,7 +32,7 @@ Please consider using a virtual machine to run GitLab.
## Ruby versions
-GitLab requires Ruby (MRI) 2.1.x or 2.2.x and currently does not work with version 2.3.
+GitLab requires Ruby (MRI) 2.1.x and currently does not work with versions 2.2 or 2.3.
You will have to use the standard MRI implementation of Ruby.
We love [JRuby](http://jruby.org/) and [Rubinius](http://rubini.us/) but GitLab
@@ -67,8 +67,8 @@ You need at least 2GB of addressable memory (RAM + swap) to install and use GitL
With less memory GitLab will give strange errors during the reconfigure run and 500 errors during usage.
- 512MB RAM + 1.5GB of swap is the absolute minimum but we strongly **advise against** this amount of memory. See the unicorn worker section below for more advice.
-- 1GB RAM + 1GB swap supports up to 100 users but it will be slow
-- **2GB RAM** is the **recommended** memory size and supports up to 100 users
+- 1GB RAM + 1GB swap supports up to 100 users but it will be very slow
+- **2GB RAM** is the **recommended** memory size for all installations and supports up to 100 users
- 4GB RAM supports up to 1,000 users
- 8GB RAM supports up to 2,000 users
- 16GB RAM supports up to 4,000 users
diff --git a/doc/integration/README.md b/doc/integration/README.md
index 281eea8363d..7c8f785a61f 100644
--- a/doc/integration/README.md
+++ b/doc/integration/README.md
@@ -39,3 +39,34 @@ please see the [project_services directory][projects-code].
[jenkins]: http://doc.gitlab.com/ee/integration/jenkins.html
[Project Service]: ../project_services/project_services.md
[projects-code]: https://gitlab.com/gitlab-org/gitlab-ce/tree/master/app/models/project_services
+
+## SSL certificate errors
+
+When trying to integrate GitLab with services that are using self-signed certificates,
+it is very likely that SSL certificate errors will occur on different parts of the
+application, most likely Sidekiq. There are 2 approaches you can take to solve this:
+
+1. Add the root certificate to the trusted chain of the OS.
+1. If using Omnibus, you can add the certificate to GitLab's trusted certificates.
+
+**OS main trusted chain**
+
+This [resource](http://kb.kerio.com/product/kerio-connect/server-configuration/ssl-certificates/adding-trusted-root-certificates-to-the-server-1605.html)
+has all the information you need to add a certificate to the main trusted chain.
+
+This [answer](http://superuser.com/questions/437330/how-do-you-add-a-certificate-authority-ca-to-ubuntu)
+at SuperUser also has relevant information.
+
+**Omnibus Trusted Chain**
+
+It is enough to concatenate the certificate to the main trusted certificate:
+
+```bash
+cat jira.pem >> /opt/gitlab/embedded/ssl/certs/cacert.pem
+```
+
+After that restart GitLab with:
+
+```bash
+sudo gitlab-ctl restart
+```
diff --git a/doc/integration/ldap.md b/doc/integration/ldap.md
index f256477196b..cf1f98492ea 100644
--- a/doc/integration/ldap.md
+++ b/doc/integration/ldap.md
@@ -204,3 +204,25 @@ 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
+```
+
diff --git a/doc/integration/omniauth.md b/doc/integration/omniauth.md
index 8e6627b2be5..ba47cb16265 100644
--- a/doc/integration/omniauth.md
+++ b/doc/integration/omniauth.md
@@ -1,8 +1,11 @@
# OmniAuth
-GitLab leverages OmniAuth to allow users to sign in using Twitter, GitHub, and other popular services.
+GitLab leverages OmniAuth to allow users to sign in using Twitter, GitHub, and
+other popular services.
-Configuring OmniAuth does not prevent standard GitLab authentication or LDAP (if configured) from continuing to work. Users can choose to sign in using any of the configured mechanisms.
+Configuring OmniAuth does not prevent standard GitLab authentication or LDAP
+(if configured) from continuing to work. Users can choose to sign in using any
+of the configured mechanisms.
- [Initial OmniAuth Configuration](#initial-omniauth-configuration)
- [Supported Providers](#supported-providers)
@@ -28,17 +31,25 @@ contains some settings that are common for all providers.
## Initial OmniAuth Configuration
-Before configuring individual OmniAuth providers there are a few global settings that are in common for all providers that we need to consider.
+Before configuring individual OmniAuth providers there are a few global settings
+that are in common for all providers that we need to consider.
- Omniauth needs to be enabled, see details below for example.
-- `allow_single_sign_on` defaults to `false`. If `false` users must be created manually or they will not be able to
-sign in via OmniAuth.
-- `block_auto_created_users` defaults to `true`. If `true` auto created users will be blocked by default and will
-have to be unblocked by an administrator before they are able to sign in.
-- **Note:** If you set `allow_single_sign_on` to `true` and `block_auto_created_users` to `false` please be aware
-that any user on the Internet will be able to successfully sign in to your GitLab without administrative approval.
-
-If you want to change these settings:
+- `allow_single_sign_on` allows you to specify the providers you want to allow to
+ automatically create an account. It defaults to `false`. If `false` users must
+ be created manually or they will not be able to sign in via OmniAuth.
+- `block_auto_created_users` defaults to `true`. If `true` auto created users will
+ be blocked by default and will have to be unblocked by an administrator before
+ they are able to sign in.
+
+>**Note:**
+If you set `block_auto_created_users` to `false`, make sure to only
+define providers under `allow_single_sign_on` that you are able to control, like
+SAML, Shibboleth, Crowd or Google, or set it to `false` otherwise any user on
+the Internet will be able to successfully sign in to your GitLab without
+administrative approval.
+
+To change these settings:
* **For omnibus package**
@@ -48,11 +59,16 @@ If you want to change these settings:
sudo editor /etc/gitlab/gitlab.rb
```
- and change
+ and change:
- ```
+ ```ruby
gitlab_rails['omniauth_enabled'] = true
- gitlab_rails['omniauth_allow_single_sign_on'] = false
+
+ # CAUTION!
+ # This allows users to login without having a user account first. Define the allowed providers
+ # using an array, e.g. ["saml", "twitter"], or as true/false to allow all providers or none.
+ # User accounts will be created automatically when authentication was successful.
+ gitlab_rails['omniauth_allow_single_sign_on'] = ['saml', 'twitter']
gitlab_rails['omniauth_block_auto_created_users'] = true
```
@@ -66,43 +82,57 @@ If you want to change these settings:
sudo -u git -H editor config/gitlab.yml
```
- and change the following section
+ and change the following section:
- ```
+ ```yaml
## OmniAuth settings
omniauth:
# Allow login via Twitter, Google, etc. using OmniAuth providers
enabled: true
# CAUTION!
- # This allows users to login without having a user account first (default: false).
+ # This allows users to login without having a user account first. Define the allowed providers
+ # using an array, e.g. ["saml", "twitter"], or as true/false to allow all providers or none.
# User accounts will be created automatically when authentication was successful.
- allow_single_sign_on: false
+ allow_single_sign_on: ["saml", "twitter"]
+
# Locks down those users until they have been cleared by the admin (default: true).
block_auto_created_users: true
```
-Now we can choose one or more of the Supported Providers below to continue configuration.
+Now we can choose one or more of the Supported Providers listed above to continue
+the configuration process.
## Enable OmniAuth for an Existing User
-Existing users can enable OmniAuth for specific providers after the account is created. For example, if the user originally signed in with LDAP an OmniAuth provider such as Twitter can be enabled. Follow the steps below to enable an OmniAuth provider for an existing user.
+Existing users can enable OmniAuth for specific providers after the account is
+created. For example, if the user originally signed in with LDAP, an OmniAuth
+provider such as Twitter can be enabled. Follow the steps below to enable an
+OmniAuth provider for an existing user.
1. Sign in normally - whether standard sign in, LDAP, or another OmniAuth provider.
1. Go to profile settings (the silhouette icon in the top right corner).
1. Select the "Account" tab.
1. Under "Connected Accounts" select the desired OmniAuth provider, such as Twitter.
-1. The user will be redirected to the provider. Once the user authorized GitLab they will be redirected back to GitLab.
+1. The user will be redirected to the provider. Once the user authorized GitLab
+ they will be redirected back to GitLab.
The chosen OmniAuth provider is now active and can be used to sign in to GitLab from then on.
## Using Custom Omniauth Providers
-GitLab uses [Omniauth](http://www.omniauth.org/) for authentication and already ships with a few providers preinstalled (e.g. LDAP, GitHub, Twitter). But sometimes that is not enough and you need to integrate with other authentication solutions. For these cases you can use the Omniauth provider.
+>**Note:**
+The following information only applies for installations from source.
+
+GitLab uses [Omniauth](http://www.omniauth.org/) for authentication and already ships
+with a few providers pre-installed (e.g. LDAP, GitHub, Twitter). But sometimes that
+is not enough and you need to integrate with other authentication solutions. For
+these cases you can use the Omniauth provider.
### Steps
-These steps are fairly general and you will need to figure out the exact details from the Omniauth provider's documentation.
+These steps are fairly general and you will need to figure out the exact details
+from the Omniauth provider's documentation.
- Stop GitLab:
@@ -128,8 +158,12 @@ These steps are fairly general and you will need to figure out the exact details
### Examples
-If you have successfully set up a provider that is not shipped with GitLab itself, please let us know.
+If you have successfully set up a provider that is not shipped with GitLab itself,
+please let us know.
-You can help others by reporting successful configurations and probably share a few insights or provide warnings for common errors or pitfalls by sharing your experience [in the public Wiki](https://github.com/gitlabhq/gitlab-public-wiki/wiki/Custom-omniauth-provider-configurations).
+You can help others by reporting successful configurations and probably share a
+few insights or provide warnings for common errors or pitfalls by sharing your
+experience [in the public Wiki](https://github.com/gitlabhq/gitlab-public-wiki/wiki/Custom-omniauth-provider-configurations).
-While we can't officially support every possible authentication mechanism out there, we'd like to at least help those with specific needs.
+While we can't officially support every possible authentication mechanism out there,
+we'd like to at least help those with specific needs.
diff --git a/doc/integration/saml.md b/doc/integration/saml.md
index 8841dbdb7c6..148c4ac1886 100644
--- a/doc/integration/saml.md
+++ b/doc/integration/saml.md
@@ -1,10 +1,14 @@
# SAML OmniAuth Provider
-GitLab can be configured to act as a SAML 2.0 Service Provider (SP). This allows GitLab to consume assertions from a SAML 2.0 Identity Provider (IdP) such as Microsoft ADFS to authenticate users.
+GitLab can be configured to act as a SAML 2.0 Service Provider (SP). This allows
+GitLab to consume assertions from a SAML 2.0 Identity Provider (IdP) such as
+Microsoft ADFS to authenticate users.
-First configure SAML 2.0 support in GitLab, then register the GitLab application in your SAML IdP:
+First configure SAML 2.0 support in GitLab, then register the GitLab application
+in your SAML IdP:
-1. Make sure GitLab is configured with HTTPS. See [Using HTTPS](../install/installation.md#using-https) for instructions.
+1. Make sure GitLab is configured with HTTPS.
+ See [Using HTTPS](../install/installation.md#using-https) for instructions.
1. On your GitLab server, open the configuration file.
@@ -22,7 +26,40 @@ First configure SAML 2.0 support in GitLab, then register the GitLab application
sudo -u git -H editor config/gitlab.yml
```
-1. See [Initial OmniAuth Configuration](omniauth.md#initial-omniauth-configuration) for initial settings.
+1. See [Initial OmniAuth Configuration](omniauth.md#initial-omniauth-configuration)
+ for initial settings.
+
+1. To allow your users to use SAML to sign up without having to manually create
+ an account first, don't forget to add the following values to your configuration:
+
+ For omnibus package:
+
+ ```ruby
+ gitlab_rails['omniauth_allow_single_sign_on'] = ['saml']
+ gitlab_rails['omniauth_block_auto_created_users'] = false
+ ```
+
+ For installations from source:
+
+ ```yaml
+ allow_single_sign_on: ["saml"]
+ block_auto_created_users: false
+ ```
+
+1. You can also automatically link SAML users with existing GitLab users if their
+ email addresses match by adding the following setting:
+
+ For omnibus package:
+
+ ```ruby
+ gitlab_rails['omniauth_auto_link_saml_user'] = true
+ ```
+
+ For installations from source:
+
+ ```yaml
+ auto_link_saml_user: true
+ ```
1. Add the provider configuration:
@@ -31,15 +68,15 @@ First configure SAML 2.0 support in GitLab, then register the GitLab application
```ruby
gitlab_rails['omniauth_providers'] = [
{
- "name" => "saml",
- args: {
+ name: 'saml',
+ args: {
assertion_consumer_service_url: 'https://gitlab.example.com/users/auth/saml/callback',
idp_cert_fingerprint: '43:51:43:a1:b5:fc:8b:b7:0a:3a:a9:b1:0f:66:73:a8',
idp_sso_target_url: 'https://login.example.com/idp',
issuer: 'https://gitlab.example.com',
name_identifier_format: 'urn:oasis:names:tc:SAML:2.0:nameid-format:transient'
},
- "label" => "Company Login" # optional label for SAML login button, defaults to "Saml"
+ label: 'Company Login' # optional label for SAML login button, defaults to "Saml"
}
]
```
@@ -47,42 +84,64 @@ First configure SAML 2.0 support in GitLab, then register the GitLab application
For installations from source:
```yaml
- - { name: 'saml',
- args: {
- assertion_consumer_service_url: 'https://gitlab.example.com/users/auth/saml/callback',
- idp_cert_fingerprint: '43:51:43:a1:b5:fc:8b:b7:0a:3a:a9:b1:0f:66:73:a8',
- idp_sso_target_url: 'https://login.example.com/idp',
- issuer: 'https://gitlab.example.com',
- name_identifier_format: 'urn:oasis:names:tc:SAML:2.0:nameid-format:transient'
- } }
+ - {
+ name: 'saml',
+ args: {
+ assertion_consumer_service_url: 'https://gitlab.example.com/users/auth/saml/callback',
+ idp_cert_fingerprint: '43:51:43:a1:b5:fc:8b:b7:0a:3a:a9:b1:0f:66:73:a8',
+ idp_sso_target_url: 'https://login.example.com/idp',
+ issuer: 'https://gitlab.example.com',
+ name_identifier_format: 'urn:oasis:names:tc:SAML:2.0:nameid-format:transient'
+ },
+ label: 'Company Login' # optional label for SAML login button, defaults to "Saml"
+ }
```
-1. Change the value for 'assertion_consumer_service_url' to match the HTTPS endpoint of GitLab (append 'users/auth/saml/callback' to the HTTPS URL of your GitLab installation to generate the correct value).
+1. Change the value for `assertion_consumer_service_url` to match the HTTPS endpoint
+ of GitLab (append `users/auth/saml/callback` to the HTTPS URL of your GitLab
+ installation to generate the correct value).
-1. Change the values of 'idp_cert_fingerprint', 'idp_sso_target_url', 'name_identifier_format' to match your IdP. Check [the omniauth-saml documentation](https://github.com/PracticallyGreen/omniauth-saml) for details on these options.
+1. Change the values of `idp_cert_fingerprint`, `idp_sso_target_url`,
+ `name_identifier_format` to match your IdP. Check
+ [the omniauth-saml documentation](https://github.com/omniauth/omniauth-saml)
+ for details on these options.
-1. Change the value of 'issuer' to a unique name, which will identify the application to the IdP.
+1. Change the value of `issuer` to a unique name, which will identify the application
+ to the IdP.
1. Restart GitLab for the changes to take effect.
-1. Register the GitLab SP in your SAML 2.0 IdP, using the application name specified in 'issuer'.
+1. Register the GitLab SP in your SAML 2.0 IdP, using the application name specified
+ in `issuer`.
-To ease configuration, most IdP accept a metadata URL for the application to provide configuration information to the IdP. To build the metadata URL for GitLab, append 'users/auth/saml/metadata' to the HTTPS URL of your GitLab installation, for instance:
+To ease configuration, most IdP accept a metadata URL for the application to provide
+configuration information to the IdP. To build the metadata URL for GitLab, append
+`users/auth/saml/metadata` to the HTTPS URL of your GitLab installation, for instance:
```
https://gitlab.example.com/users/auth/saml/metadata
```
-At a minimum the IdP *must* provide a claim containing the user's email address, using claim name 'email' or 'mail'. The email will be used to automatically generate the GitLab username. GitLab will also use claims with name 'name', 'first_name', 'last_name' (see [the omniauth-saml gem](https://github.com/PracticallyGreen/omniauth-saml/blob/master/lib/omniauth/strategies/saml.rb) for supported claims).
+At a minimum the IdP *must* provide a claim containing the user's email address, using
+claim name `email` or `mail`. The email will be used to automatically generate the GitLab
+username. GitLab will also use claims with name `name`, `first_name`, `last_name`
+(see [the omniauth-saml gem](https://github.com/omniauth/omniauth-saml/blob/master/lib/omniauth/strategies/saml.rb)
+for supported claims).
-On the sign in page there should now be a SAML button below the regular sign in form. Click the icon to begin the authentication process. If everything goes well the user will be returned to GitLab and will be signed in.
+On the sign in page there should now be a SAML button below the regular sign in form.
+Click the icon to begin the authentication process. If everything goes well the user
+will be returned to GitLab and will be signed in.
## Troubleshooting
+### 500 error after login
+
If you see a "500 error" in GitLab when you are redirected back from the SAML sign in page,
this likely indicates that GitLab could not get the email address for the SAML user.
Make sure the IdP provides a claim containing the user's email address, using claim name
-'email' or 'mail'. The email will be used to automatically generate the GitLab username.
+`email` or `mail`.
+
+### Redirect back to login screen with no evident error
If after signing in into your SAML server you are redirected back to the sign in page and
no error is displayed, check your `production.log` file. It will most likely contain the
@@ -92,4 +151,36 @@ the SAML request, but this error never reaches GitLab due to the CSRF check.
To bypass this you can add `skip_before_action :verify_authenticity_token` to the
`omniauth_callbacks_controller.rb` file. This will allow the error to hit GitLab,
where it can then be seen in the usual logs, or as a flash message in the login
-screen. \ No newline at end of file
+screen.
+
+### Invalid audience
+
+This error means that the IdP doesn't recognize GitLab as a valid sender and
+receiver of SAML requests. Make sure to add the GitLab callback URL to the approved
+audiences of the IdP server.
+
+### Missing claims
+
+The IdP server needs to pass certain information in order for GitLab to either
+create an account, or match the login information to an existing account. `email`
+is the minimum amount of information that needs to be passed. If the IdP server
+is not providing this information, all SAML requests will fail.
+
+Make sure this information is provided.
+
+### Key validation error, Digest mismatch or Fingerprint mismatch
+
+These errors all come from a similar place, the SAML certificate. SAML requests
+need to be validated using a fingerprint, a certificate or a validator.
+
+For this you need take the following into account:
+
+- If no certificate is provided in the settings, a fingerprint or fingerprint
+ validator needs to be provided and the response from the server must contain
+ a certificate (`<ds:KeyInfo><ds:X509Data><ds:X509Certificate>`)
+- If a certificate is provided in the settings, it is no longer necessary for
+ the request to contain one. In this case the fingerprint or fingerprint
+ validators are optional
+
+Make sure that one of the above described scenarios is valid, or the requests will
+fail with one of the mentioned errors. \ No newline at end of file
diff --git a/doc/legal/individual_contributor_license_agreement.md b/doc/legal/individual_contributor_license_agreement.md
index f97c252fd7c..59803aea080 100644
--- a/doc/legal/individual_contributor_license_agreement.md
+++ b/doc/legal/individual_contributor_license_agreement.md
@@ -18,7 +18,7 @@ You accept and agree to the following terms and conditions for Your present and
6. You are not expected to provide support for Your Contributions, except to the extent You desire to provide support. You may provide support for free, for a fee, or not at all. Unless required by applicable law or agreed to in writing, You provide Your Contributions on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON- INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE.
-7. Should You wish to submit work that is not Your original creation, You may submit it to GitLab B.V. separately from any Contribution, identifying the complete details of its source and of any license or other restriction (including, but not limited to, related patents, trademarks, and license agreements) of which you are personally aware, and conspicuously marking the work as "Submitted on behalf of a third-party: [[]named here]".
+7. Should You wish to submit work that is not Your original creation, You may submit it to GitLab B.V. separately from any Contribution, identifying the complete details of its source and of any license or other restriction (including, but not limited to, related patents, trademarks, and license agreements) of which you are personally aware, and conspicuously marking the work as "Submitted on behalf of a third-party: [insert_name_here]".
8. You agree to notify GitLab B.V. of any facts or circumstances of which you become aware that would make these representations inaccurate in any respect.
diff --git a/doc/markdown/markdown.md b/doc/markdown/markdown.md
index c400cdac64d..cbf57db5684 100644
--- a/doc/markdown/markdown.md
+++ b/doc/markdown/markdown.md
@@ -207,6 +207,7 @@ GFM also recognizes certain cross-project references:
| `namespace/project$123` | snippet |
| `namespace/project@9ba12248` | specific commit |
| `namespace/project@9ba12248...b19a04f5` | commit range comparison |
+| `namespace/project~"Some label"` | issues with given label |
## Task Lists
diff --git a/doc/permissions/permissions.md b/doc/permissions/permissions.md
index 168e7d143ee..ac0fd3d1756 100644
--- a/doc/permissions/permissions.md
+++ b/doc/permissions/permissions.md
@@ -6,7 +6,7 @@ If a user is both in a project group and in the project itself, the highest perm
If a user is a GitLab administrator they receive all permissions.
-On public projects the Guest role is not enforced.
+On public and internal projects the Guest role is not enforced.
All users will be able to create issues, leave comments, and pull or download the project code.
To add or import a user, you can follow the [project users and members
@@ -26,6 +26,7 @@ documentation](../workflow/add-user/add-user.md).
| Create code snippets | | ✓ | ✓ | ✓ | ✓ |
| Manage issue tracker | | ✓ | ✓ | ✓ | ✓ |
| Manage labels | | ✓ | ✓ | ✓ | ✓ |
+| See a commit status | | ✓ | ✓ | ✓ | ✓ |
| Manage merge requests | | | ✓ | ✓ | ✓ |
| Create new merge request | | | ✓ | ✓ | ✓ |
| Create new branches | | | ✓ | ✓ | ✓ |
@@ -35,6 +36,7 @@ documentation](../workflow/add-user/add-user.md).
| Add tags | | | ✓ | ✓ | ✓ |
| Write a wiki | | | ✓ | ✓ | ✓ |
| Cancel and retry builds | | | ✓ | ✓ | ✓ |
+| Create or update commit status | | | ✓ | ✓ | ✓ |
| Create new milestones | | | | ✓ | ✓ |
| Add new team members | | | | ✓ | ✓ |
| Push to protected branches | | | | ✓ | ✓ |
diff --git a/doc/project_services/builds_emails.md b/doc/project_services/builds_emails.md
new file mode 100644
index 00000000000..af0b1a287c7
--- /dev/null
+++ b/doc/project_services/builds_emails.md
@@ -0,0 +1,16 @@
+## Enabling build emails
+
+To receive e-mail notifications about the result status of your builds, visit
+your project's **Settings > Services > Builds emails** and activate the service.
+
+In the _Recipients_ area, provide a list of e-mails separated by comma.
+
+Check the _Add pusher_ checkbox if you want the committer to also receive
+e-mail notifications about each build's status.
+
+If you enable the _Notify only broken builds_ option, e-mail notifications will
+be sent only for failed builds.
+
+---
+
+![Builds emails service settings](img/builds_emails_service.png)
diff --git a/doc/project_services/img/builds_emails_service.png b/doc/project_services/img/builds_emails_service.png
new file mode 100644
index 00000000000..e604dd73ffa
--- /dev/null
+++ b/doc/project_services/img/builds_emails_service.png
Binary files differ
diff --git a/doc/project_services/jira.md b/doc/project_services/jira.md
index 7c12557a321..27170c1eb19 100644
--- a/doc/project_services/jira.md
+++ b/doc/project_services/jira.md
@@ -219,3 +219,16 @@ You can see from the above image that there are four references to GitLab:
[JIRA Core]: https://www.atlassian.com/software/jira/core "The JIRA Core website"
[jira-ce]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/2146 "MR - Backport JIRA service"
[8_3_post]: https://about.gitlab.com/2015/12/22/gitlab-8-3-released/ "GitLab 8.3 release post"
+
+## Troubleshooting
+
+### GitLab is unable to comment on a ticket
+
+Make sure that the user you set up for GitLab to communicate with JIRA has the
+correct access permission to post comments on a ticket and to also transition the
+ticket, if you'd like GitLab to also take care of closing them.
+
+### GitLab is unable to close a ticket
+
+Make sure the the `Transition ID` you set within the JIRA settings matches the
+one your project needs to close a ticket.
diff --git a/doc/project_services/project_services.md b/doc/project_services/project_services.md
index 55db3e4f2f3..3fea2cff0b9 100644
--- a/doc/project_services/project_services.md
+++ b/doc/project_services/project_services.md
@@ -12,7 +12,7 @@ further configuration instructions and details. Contributions are welcome.
| Assembla | Project Management Software (Source Commits Endpoint) |
| [Atlassian Bamboo CI](bamboo.md) | A continuous integration and build server |
| Buildkite | Continuous integration and deployments |
-| Builds emails | Email the builds status to a list of recipients |
+| [Builds emails](builds_emails.md) | Email the builds status to a list of recipients |
| Campfire | Simple web-based real-time group chat |
| Custom Issue Tracker | Custom issue tracker |
| Drone CI | Continuous Integration platform built on Docker, written in Go |
diff --git a/doc/update/8.3-to-8.4.md b/doc/update/8.3-to-8.4.md
index 616b1f58b65..269deec7a9c 100644
--- a/doc/update/8.3-to-8.4.md
+++ b/doc/update/8.3-to-8.4.md
@@ -81,27 +81,6 @@ There are new configuration options available for [`gitlab.yml`](config/gitlab.y
git diff origin/8-3-stable:config/gitlab.yml.example origin/8-4-stable:config/gitlab.yml.example
```
-#### Nginx configuration
-
-GitLab 8.3 introduced major changes in the NGINX configuration. Ensure you're
-still up-to-date with the latest changes:
-
-```sh
-# For HTTPS configurations
-git diff origin/8-3-stable:lib/support/nginx/gitlab-ssl origin/8-4-stable:lib/support/nginx/gitlab-ssl
-
-# For HTTP configurations
-git diff origin/8-3-stable:lib/support/nginx/gitlab origin/8-4-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-4-stable/lib/support/init.d/gitlab.default.example#L34
-
#### Init script
We updated the init script for GitLab in order to set a specific PATH for gitlab-workhorse.
diff --git a/doc/update/8.4-to-8.5.md b/doc/update/8.4-to-8.5.md
index d323131ab7d..0a9cb5683e7 100644
--- a/doc/update/8.4-to-8.5.md
+++ b/doc/update/8.4-to-8.5.md
@@ -85,6 +85,32 @@ There are new configuration options available for [`gitlab.yml`](config/gitlab.y
git diff origin/8-4-stable:config/gitlab.yml.example origin/8-5-stable:config/gitlab.yml.example
```
+#### Nginx configuration
+
+Ensure you're still up-to-date with the latest NGINX configuration changes:
+
+```sh
+# For HTTPS configurations
+git diff origin/8-4-stable:lib/support/nginx/gitlab-ssl origin/8-5-stable:lib/support/nginx/gitlab-ssl
+
+# For HTTP configurations
+git diff origin/8-4-stable:lib/support/nginx/gitlab origin/8-5-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-5-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
diff --git a/doc/web_hooks/web_hooks.md b/doc/web_hooks/web_hooks.md
index c556597225c..b82306bd1da 100644
--- a/doc/web_hooks/web_hooks.md
+++ b/doc/web_hooks/web_hooks.md
@@ -1,5 +1,12 @@
# Web hooks
+_**Note:**
+Starting from GitLab 8.5:_
+
+- _the `repository` key is deprecated in favor of the `project` key_
+- _the `project.ssh_url` key is deprecated in favor of the `project.git_ssh_url` key_
+- _the `project.http_url` key is deprecated in favor of the `project.git_http_url` key_
+
Project web hooks allow you to trigger an URL if new code is pushed or a new issue is created.
You can configure web hooks to listen for specific events like pushes, issues or merge requests. GitLab will send a POST request with data to the web hook URL.
@@ -37,8 +44,25 @@ X-Gitlab-Event: Push Hook
"user_id": 4,
"user_name": "John Smith",
"user_email": "john@example.com",
+ "user_avatar": "https://s.gravatar.com/avatar/d4c74594d841139328695756648b6bd6?s=8://s.gravatar.com/avatar/d4c74594d841139328695756648b6bd6?s=80",
"project_id": 15,
- "repository": {
+ "project":{
+ "name":"Diaspora",
+ "description":"",
+ "web_url":"http://example.com/mike/diaspora",
+ "avatar_url":null,
+ "git_ssh_url":"git@example.com:mike/diaspora.git",
+ "git_http_url":"http://example.com/mike/diaspora.git",
+ "namespace":"Mike",
+ "visibility_level":0,
+ "path_with_namespace":"mike/diaspora",
+ "default_branch":"master",
+ "homepage":"http://example.com/mike/diaspora",
+ "url":"git@example.com:mike/diasporadiaspora.git",
+ "ssh_url":"git@example.com:mike/diaspora.git",
+ "http_url":"http://example.com/mike/diaspora.git"
+ },
+ "repository":{
"name": "Diaspora",
"url": "git@example.com:mike/diasporadiaspora.git",
"description": "",
@@ -100,8 +124,25 @@ X-Gitlab-Event: Tag Push Hook
"after": "82b3d5ae55f7080f1e6022629cdb57bfae7cccc7",
"user_id": 1,
"user_name": "John Smith",
+ "user_avatar": "https://s.gravatar.com/avatar/d4c74594d841139328695756648b6bd6?s=8://s.gravatar.com/avatar/d4c74594d841139328695756648b6bd6?s=80",
"project_id": 1,
- "repository": {
+ "project":{
+ "name":"Example",
+ "description":"",
+ "web_url":"http://example.com/jsmith/example",
+ "avatar_url":null,
+ "git_ssh_url":"git@example.com:jsmith/example.git",
+ "git_http_url":"http://example.com/jsmith/example.git",
+ "namespace":"Jsmith",
+ "visibility_level":0,
+ "path_with_namespace":"jsmith/example",
+ "default_branch":"master",
+ "homepage":"http://example.com/jsmith/example",
+ "url":"git@example.com:jsmith/example.git",
+ "ssh_url":"git@example.com:jsmith/example.git",
+ "http_url":"http://example.com/jsmith/example.git"
+ },
+ "repository":{
"name": "jsmith",
"url": "ssh://git@example.com/jsmith/example.git",
"description": "",
@@ -135,7 +176,23 @@ X-Gitlab-Event: Issue Hook
"username": "root",
"avatar_url": "http://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=40\u0026d=identicon"
},
- "repository": {
+ "project":{
+ "name":"Gitlab Test",
+ "description":"Aut reprehenderit ut est.",
+ "web_url":"http://example.com/gitlabhq/gitlab-test",
+ "avatar_url":null,
+ "git_ssh_url":"git@example.com:gitlabhq/gitlab-test.git",
+ "git_http_url":"http://example.com/gitlabhq/gitlab-test.git",
+ "namespace":"GitlabHQ",
+ "visibility_level":20,
+ "path_with_namespace":"gitlabhq/gitlab-test",
+ "default_branch":"master",
+ "homepage":"http://example.com/gitlabhq/gitlab-test",
+ "url":"http://example.com/gitlabhq/gitlab-test.git",
+ "ssh_url":"git@example.com:gitlabhq/gitlab-test.git",
+ "http_url":"http://example.com/gitlabhq/gitlab-test.git"
+ },
+ "repository":{
"name": "Gitlab Test",
"url": "http://example.com/gitlabhq/gitlab-test.git",
"description": "Aut reprehenderit ut est.",
@@ -197,9 +254,25 @@ X-Gitlab-Event: Note Hook
"avatar_url": "http://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=40\u0026d=identicon"
},
"project_id": 5,
- "repository": {
+ "project":{
+ "name":"Gitlab Test",
+ "description":"Aut reprehenderit ut est.",
+ "web_url":"http://example.com/gitlabhq/gitlab-test",
+ "avatar_url":null,
+ "git_ssh_url":"git@example.com:gitlabhq/gitlab-test.git",
+ "git_http_url":"http://example.com/gitlabhq/gitlab-test.git",
+ "namespace":"GitlabHQ",
+ "visibility_level":20,
+ "path_with_namespace":"gitlabhq/gitlab-test",
+ "default_branch":"master",
+ "homepage":"http://example.com/gitlabhq/gitlab-test",
+ "url":"http://example.com/gitlabhq/gitlab-test.git",
+ "ssh_url":"git@example.com:gitlabhq/gitlab-test.git",
+ "http_url":"http://example.com/gitlabhq/gitlab-test.git"
+ },
+ "repository":{
"name": "Gitlab Test",
- "url": "http://localhost/gitlab-org/gitlab-test.git",
+ "url": "http://example.com/gitlab-org/gitlab-test.git",
"description": "Aut reprehenderit ut est.",
"homepage": "http://example.com/gitlab-org/gitlab-test"
},
@@ -260,9 +333,25 @@ X-Gitlab-Event: Note Hook
"avatar_url": "http://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=40\u0026d=identicon"
},
"project_id": 5,
- "repository": {
+ "project":{
+ "name":"Gitlab Test",
+ "description":"Aut reprehenderit ut est.",
+ "web_url":"http://example.com/gitlab-org/gitlab-test",
+ "avatar_url":null,
+ "git_ssh_url":"git@example.com:gitlab-org/gitlab-test.git",
+ "git_http_url":"http://example.com/gitlab-org/gitlab-test.git",
+ "namespace":"Gitlab Org",
+ "visibility_level":10,
+ "path_with_namespace":"gitlab-org/gitlab-test",
+ "default_branch":"master",
+ "homepage":"http://example.com/gitlab-org/gitlab-test",
+ "url":"http://example.com/gitlab-org/gitlab-test.git",
+ "ssh_url":"git@example.com:gitlab-org/gitlab-test.git",
+ "http_url":"http://example.com/gitlab-org/gitlab-test.git"
+ },
+ "repository":{
"name": "Gitlab Test",
- "url": "http://example.com/gitlab-org/gitlab-test.git",
+ "url": "http://localhost/gitlab-org/gitlab-test.git",
"description": "Aut reprehenderit ut est.",
"homepage": "http://example.com/gitlab-org/gitlab-test"
},
@@ -300,21 +389,37 @@ X-Gitlab-Event: Note Hook
"description": "Et voluptas corrupti assumenda temporibus. Architecto cum animi eveniet amet asperiores. Vitae numquam voluptate est natus sit et ad id.",
"position": 0,
"locked_at": null,
- "source": {
- "name": "Gitlab Test",
- "ssh_url": "git@example.com:gitlab-org/gitlab-test.git",
- "http_url": "http://example.com/gitlab-org/gitlab-test.git",
- "web_url": "http://example.com/gitlab-org/gitlab-test",
- "namespace": "Gitlab Org",
- "visibility_level": 10
+ "source":{
+ "name":"Gitlab Test",
+ "description":"Aut reprehenderit ut est.",
+ "web_url":"http://example.com/gitlab-org/gitlab-test",
+ "avatar_url":null,
+ "git_ssh_url":"git@example.com:gitlab-org/gitlab-test.git",
+ "git_http_url":"http://example.com/gitlab-org/gitlab-test.git",
+ "namespace":"Gitlab Org",
+ "visibility_level":10,
+ "path_with_namespace":"gitlab-org/gitlab-test",
+ "default_branch":"master",
+ "homepage":"http://example.com/gitlab-org/gitlab-test",
+ "url":"http://example.com/gitlab-org/gitlab-test.git",
+ "ssh_url":"git@example.com:gitlab-org/gitlab-test.git",
+ "http_url":"http://example.com/gitlab-org/gitlab-test.git"
},
"target": {
- "name": "Gitlab Test",
- "ssh_url": "git@example.com:gitlab-org/gitlab-test.git",
- "http_url": "http://example.com/gitlab-org/gitlab-test.git",
- "web_url": "http://example.com/gitlab-org/gitlab-test",
- "namespace": "Gitlab Org",
- "visibility_level": 10
+ "name":"Gitlab Test",
+ "description":"Aut reprehenderit ut est.",
+ "web_url":"http://example.com/gitlab-org/gitlab-test",
+ "avatar_url":null,
+ "git_ssh_url":"git@example.com:gitlab-org/gitlab-test.git",
+ "git_http_url":"http://example.com/gitlab-org/gitlab-test.git",
+ "namespace":"Gitlab Org",
+ "visibility_level":10,
+ "path_with_namespace":"gitlab-org/gitlab-test",
+ "default_branch":"master",
+ "homepage":"http://example.com/gitlab-org/gitlab-test",
+ "url":"http://example.com/gitlab-org/gitlab-test.git",
+ "ssh_url":"git@example.com:gitlab-org/gitlab-test.git",
+ "http_url":"http://example.com/gitlab-org/gitlab-test.git"
},
"last_commit": {
"id": "562e173be03b8ff2efb05345d12df18815438a4b",
@@ -355,11 +460,27 @@ X-Gitlab-Event: Note Hook
"avatar_url": "http://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=40\u0026d=identicon"
},
"project_id": 5,
- "repository": {
- "name": "Gitlab Test",
- "url": "http://example.com/gitlab-org/gitlab-test.git",
- "description": "Aut reprehenderit ut est.",
- "homepage": "http://example.com/gitlab-org/gitlab-test"
+ "project":{
+ "name":"Gitlab Test",
+ "description":"Aut reprehenderit ut est.",
+ "web_url":"http://example.com/gitlab-org/gitlab-test",
+ "avatar_url":null,
+ "git_ssh_url":"git@example.com:gitlab-org/gitlab-test.git",
+ "git_http_url":"http://example.com/gitlab-org/gitlab-test.git",
+ "namespace":"Gitlab Org",
+ "visibility_level":10,
+ "path_with_namespace":"gitlab-org/gitlab-test",
+ "default_branch":"master",
+ "homepage":"http://example.com/gitlab-org/gitlab-test",
+ "url":"http://example.com/gitlab-org/gitlab-test.git",
+ "ssh_url":"git@example.com:gitlab-org/gitlab-test.git",
+ "http_url":"http://example.com/gitlab-org/gitlab-test.git"
+ },
+ "repository":{
+ "name":"diaspora",
+ "url":"git@example.com:mike/diasporadiaspora.git",
+ "description":"",
+ "homepage":"http://example.com/mike/diaspora"
},
"object_attributes": {
"id": 1241,
@@ -397,7 +518,6 @@ X-Gitlab-Event: Note Hook
### Comment on code snippet
-
**Request header**:
```
@@ -415,11 +535,27 @@ X-Gitlab-Event: Note Hook
"avatar_url": "http://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=40\u0026d=identicon"
},
"project_id": 5,
- "repository": {
- "name": "Gitlab Test",
- "url": "http://example.com/gitlab-org/gitlab-test.git",
- "description": "Aut reprehenderit ut est.",
- "homepage": "http://example.com/gitlab-org/gitlab-test"
+ "project":{
+ "name":"Gitlab Test",
+ "description":"Aut reprehenderit ut est.",
+ "web_url":"http://example.com/gitlab-org/gitlab-test",
+ "avatar_url":null,
+ "git_ssh_url":"git@example.com:gitlab-org/gitlab-test.git",
+ "git_http_url":"http://example.com/gitlab-org/gitlab-test.git",
+ "namespace":"Gitlab Org",
+ "visibility_level":10,
+ "path_with_namespace":"gitlab-org/gitlab-test",
+ "default_branch":"master",
+ "homepage":"http://example.com/gitlab-org/gitlab-test",
+ "url":"http://example.com/gitlab-org/gitlab-test.git",
+ "ssh_url":"git@example.com:gitlab-org/gitlab-test.git",
+ "http_url":"http://example.com/gitlab-org/gitlab-test.git"
+ },
+ "repository":{
+ "name":"Gitlab Test",
+ "url":"http://example.com/gitlab-org/gitlab-test.git",
+ "description":"Aut reprehenderit ut est.",
+ "homepage":"http://example.com/gitlab-org/gitlab-test"
},
"object_attributes": {
"id": 1245,
@@ -491,21 +627,37 @@ X-Gitlab-Event: Merge Request Hook
"target_project_id": 14,
"iid": 1,
"description": "",
- "source": {
- "name": "awesome_project",
- "ssh_url": "ssh://git@example.com/awesome_space/awesome_project.git",
- "http_url": "http://example.com/awesome_space/awesome_project.git",
- "web_url": "http://example.com/awesome_space/awesome_project",
- "visibility_level": 20,
- "namespace": "awesome_space"
+ "source":{
+ "name":"Awesome Project",
+ "description":"Aut reprehenderit ut est.",
+ "web_url":"http://example.com/awesome_space/awesome_project",
+ "avatar_url":null,
+ "git_ssh_url":"git@example.com:awesome_space/awesome_project.git",
+ "git_http_url":"http://example.com/awesome_space/awesome_project.git",
+ "namespace":"Awesome Space",
+ "visibility_level":20,
+ "path_with_namespace":"awesome_space/awesome_project",
+ "default_branch":"master",
+ "homepage":"http://example.com/awesome_space/awesome_project",
+ "url":"http://example.com/awesome_space/awesome_project.git",
+ "ssh_url":"git@example.com:awesome_space/awesome_project.git",
+ "http_url":"http://example.com/awesome_space/awesome_project.git"
},
"target": {
- "name": "awesome_project",
- "ssh_url": "ssh://git@example.com/awesome_space/awesome_project.git",
- "http_url": "http://example.com/awesome_space/awesome_project.git",
- "web_url": "http://example.com/awesome_space/awesome_project",
- "visibility_level": 20,
- "namespace": "awesome_space"
+ "name":"Awesome Project",
+ "description":"Aut reprehenderit ut est.",
+ "web_url":"http://example.com/awesome_space/awesome_project",
+ "avatar_url":null,
+ "git_ssh_url":"git@example.com:awesome_space/awesome_project.git",
+ "git_http_url":"http://example.com/awesome_space/awesome_project.git",
+ "namespace":"Awesome Space",
+ "visibility_level":20,
+ "path_with_namespace":"awesome_space/awesome_project",
+ "default_branch":"master",
+ "homepage":"http://example.com/awesome_space/awesome_project",
+ "url":"http://example.com/awesome_space/awesome_project.git",
+ "ssh_url":"git@example.com:awesome_space/awesome_project.git",
+ "http_url":"http://example.com/awesome_space/awesome_project.git"
},
"last_commit": {
"id": "da1560886d4f094c3e6c9ef40349f7d38b5d27d7",
diff --git a/doc/workflow/README.md b/doc/workflow/README.md
index bf62ab41053..2ac32373ce9 100644
--- a/doc/workflow/README.md
+++ b/doc/workflow/README.md
@@ -17,7 +17,9 @@
- [Releases](releases.md)
- [Milestones](milestones.md)
- [Merge Requests](merge_requests.md)
+- [Revert changes](revert_changes.md)
- ["Work In Progress" Merge Requests](wip_merge_requests.md)
- [Merge When Build Succeeds](merge_when_build_succeeds.md)
- [Manage large binaries with Git LFS](lfs/manage_large_binaries_with_git_lfs.md)
- [Importing from SVN, GitHub, BitBucket, etc](importing/README.md)
+- [Todos](todos.md)
diff --git a/doc/workflow/gitlab_flow.md b/doc/workflow/gitlab_flow.md
index be32f0c720b..1b354bcc0f1 100644
--- a/doc/workflow/gitlab_flow.md
+++ b/doc/workflow/gitlab_flow.md
@@ -16,7 +16,7 @@ It offers a simple, transparent and effective way to work with git.
![Four stages (working copy, index, local repo, remote repo) and three steps between them](four_stages.png)
When converting to git you have to get used to the fact that there are three steps before a commit is shared with colleagues.
-Most version control systems have only step, committing from the working copy to a shared server.
+Most version control systems have only one step, committing from the working copy to a shared server.
In git you add files from the working copy to the staging area. After that you commit them to the local repo.
The third step is pushing to a shared remote repository.
After getting used to these three steps the branching model becomes the challenge.
@@ -187,12 +187,15 @@ If you have an issue that spans across multiple repositories, the best thing is
![Vim screen showing the rebase view](rebase.png)
With git you can use an interactive rebase (`rebase -i`) to squash multiple commits into one and reorder them.
+In GitLab EE and .com you can also [rebase before merge](http://doc.gitlab.com/ee/workflow/rebase_before_merge.html) from the web interface.
This functionality is useful if you made a couple of commits for small changes during development and want to replace them with a single commit or if you want to make the order more logical.
However you should never rebase commits you have pushed to a remote server.
Somebody can have referred to the commits or cherry-picked them.
When you rebase you change the identifier (SHA-1) of the commit and this is confusing.
If you do that the same change will be known under multiple identifiers and this can cause much confusion.
If people already reviewed your code it will be hard for them to review only the improvements you made since then if you have rebased everything into one commit.
+Another reasons not to rebase is that you lose authorship information, maybe someone created a merge request, another person pushed a commit on there to improve it and a third one merged it.
+In this case rebasing all the commits into one prevent the other authors from being properly attributed and sharing part of the [git blame](https://git-scm.com/docs/git-blame).
People are encouraged to commit often and to frequently push to the remote repository so other people are aware what everyone is working on.
This will lead to many commits per change which makes the history harder to understand.
@@ -221,13 +224,11 @@ You can reuse recorded resolutions (rerere) sometimes, but without rebasing you
There has to be a better way to avoid many merge commits.
The way to prevent creating many merge commits is to not frequently merge master into the feature branch.
-We'll discuss the three reasons to merge in master: leveraging code, solving merge conflicts and long running branches.
+We'll discuss the three reasons to merge in master: leveraging code, merge conflicts, and long running branches.
If you need to leverage some code that was introduced in master after you created the feature branch you can sometimes solve this by just cherry-picking a commit.
If your feature branch has a merge conflict, creating a merge commit is a normal way of solving this.
-You should aim to prevent merge conflicts where they are likely to occur.
-One example is the CHANGELOG file where each significant change in the codebase is documented under a version header.
-Instead of everyone adding their change at the bottom of the list for the current version it is better to randomly insert it in the current list for that version.
-This it is likely that multiple feature branches that add to the CHANGELOG can be merged before a conflict occurs.
+You can prevent some merge conflicts by using [gitattributes](http://git-scm.com/docs/gitattributes) for files that can be in a random order.
+For example in GitLab our changelog file is specified in .gitattributes as `CHANGELOG merge=union` so that there are fewer merge conflicts in it.
The last reason for creating merge commits is having long lived branches that you want to keep up to date with the latest state of the project.
Martin Fowler, in [his article about feature branches](http://martinfowler.com/bliki/FeatureBranch.html) talks about this Continuous Integration (CI).
At GitLab we are guilty of confusing CI with branch testing. Quoting Martin Fowler: "I've heard people say they are doing CI because they are running builds, perhaps using a CI server, on every branch with every commit.
diff --git a/doc/workflow/img/revert_changes_commit.png b/doc/workflow/img/revert_changes_commit.png
new file mode 100644
index 00000000000..d84211e20db
--- /dev/null
+++ b/doc/workflow/img/revert_changes_commit.png
Binary files differ
diff --git a/doc/workflow/img/revert_changes_commit_modal.png b/doc/workflow/img/revert_changes_commit_modal.png
new file mode 100644
index 00000000000..e94d151a2af
--- /dev/null
+++ b/doc/workflow/img/revert_changes_commit_modal.png
Binary files differ
diff --git a/doc/workflow/img/revert_changes_mr.png b/doc/workflow/img/revert_changes_mr.png
new file mode 100644
index 00000000000..7adad88463b
--- /dev/null
+++ b/doc/workflow/img/revert_changes_mr.png
Binary files differ
diff --git a/doc/workflow/img/revert_changes_mr_modal.png b/doc/workflow/img/revert_changes_mr_modal.png
new file mode 100644
index 00000000000..9da78f84828
--- /dev/null
+++ b/doc/workflow/img/revert_changes_mr_modal.png
Binary files differ
diff --git a/doc/workflow/img/todos_icon.png b/doc/workflow/img/todos_icon.png
new file mode 100644
index 00000000000..879b3b51c21
--- /dev/null
+++ b/doc/workflow/img/todos_icon.png
Binary files differ
diff --git a/doc/workflow/img/todos_index.png b/doc/workflow/img/todos_index.png
new file mode 100644
index 00000000000..4ee18dd1285
--- /dev/null
+++ b/doc/workflow/img/todos_index.png
Binary files differ
diff --git a/doc/workflow/img/web_editor_new_branch_dropdown.png b/doc/workflow/img/web_editor_new_branch_dropdown.png
new file mode 100644
index 00000000000..009e4b05adf
--- /dev/null
+++ b/doc/workflow/img/web_editor_new_branch_dropdown.png
Binary files differ
diff --git a/doc/workflow/img/web_editor_new_branch_page.png b/doc/workflow/img/web_editor_new_branch_page.png
new file mode 100644
index 00000000000..dd6cfc6e7bb
--- /dev/null
+++ b/doc/workflow/img/web_editor_new_branch_page.png
Binary files differ
diff --git a/doc/workflow/img/web_editor_new_directory_dialog.png b/doc/workflow/img/web_editor_new_directory_dialog.png
new file mode 100644
index 00000000000..2c76f84f395
--- /dev/null
+++ b/doc/workflow/img/web_editor_new_directory_dialog.png
Binary files differ
diff --git a/doc/workflow/img/web_editor_new_directory_dropdown.png b/doc/workflow/img/web_editor_new_directory_dropdown.png
new file mode 100644
index 00000000000..cedf46aedfd
--- /dev/null
+++ b/doc/workflow/img/web_editor_new_directory_dropdown.png
Binary files differ
diff --git a/doc/workflow/img/web_editor_new_file_dropdown.png b/doc/workflow/img/web_editor_new_file_dropdown.png
new file mode 100644
index 00000000000..6e884f6504d
--- /dev/null
+++ b/doc/workflow/img/web_editor_new_file_dropdown.png
Binary files differ
diff --git a/doc/workflow/img/web_editor_new_file_editor.png b/doc/workflow/img/web_editor_new_file_editor.png
new file mode 100644
index 00000000000..c76473bcfa7
--- /dev/null
+++ b/doc/workflow/img/web_editor_new_file_editor.png
Binary files differ
diff --git a/doc/workflow/img/web_editor_new_push_widget.png b/doc/workflow/img/web_editor_new_push_widget.png
new file mode 100644
index 00000000000..a2108735741
--- /dev/null
+++ b/doc/workflow/img/web_editor_new_push_widget.png
Binary files differ
diff --git a/doc/workflow/img/web_editor_new_tag_dropdown.png b/doc/workflow/img/web_editor_new_tag_dropdown.png
new file mode 100644
index 00000000000..263dd635b95
--- /dev/null
+++ b/doc/workflow/img/web_editor_new_tag_dropdown.png
Binary files differ
diff --git a/doc/workflow/img/web_editor_new_tag_page.png b/doc/workflow/img/web_editor_new_tag_page.png
new file mode 100644
index 00000000000..64d7cd11ed1
--- /dev/null
+++ b/doc/workflow/img/web_editor_new_tag_page.png
Binary files differ
diff --git a/doc/workflow/img/web_editor_start_new_merge_request.png b/doc/workflow/img/web_editor_start_new_merge_request.png
new file mode 100644
index 00000000000..be12a151cac
--- /dev/null
+++ b/doc/workflow/img/web_editor_start_new_merge_request.png
Binary files differ
diff --git a/doc/workflow/img/web_editor_upload_file_dialog.png b/doc/workflow/img/web_editor_upload_file_dialog.png
new file mode 100644
index 00000000000..6dd2207bca0
--- /dev/null
+++ b/doc/workflow/img/web_editor_upload_file_dialog.png
Binary files differ
diff --git a/doc/workflow/img/web_editor_upload_file_dropdown.png b/doc/workflow/img/web_editor_upload_file_dropdown.png
new file mode 100644
index 00000000000..bf6528701b0
--- /dev/null
+++ b/doc/workflow/img/web_editor_upload_file_dropdown.png
Binary files differ
diff --git a/doc/workflow/importing/migrating_from_svn.md b/doc/workflow/importing/migrating_from_svn.md
index b355a91b5a6..4828bb5dce6 100644
--- a/doc/workflow/importing/migrating_from_svn.md
+++ b/doc/workflow/importing/migrating_from_svn.md
@@ -69,6 +69,7 @@ branches and tags.
```bash
git remote add origin git@gitlab.com:<group>/<project>.git
git push --all origin
+git push --tags origin
```
## Contribute to this guide
diff --git a/doc/workflow/lfs/lfs_administration.md b/doc/workflow/lfs/lfs_administration.md
index 5076b2697a3..36cb9da2380 100644
--- a/doc/workflow/lfs/lfs_administration.md
+++ b/doc/workflow/lfs/lfs_administration.md
@@ -9,7 +9,8 @@ Documentation on how to use Git LFS are under [Managing large binary files with
## Configuration
-Git LFS objects can be large in size. By default, they are stored on the server GitLab is installed on.
+Git LFS objects can be large in size. By default, they are stored on the server
+GitLab is installed on.
There are two configuration options to help GitLab server administrators:
@@ -37,5 +38,8 @@ In `config/gitlab.yml`:
## Known limitations
-* Currently, storing GitLab Git LFS objects on a non-local storage (like S3 buckets) is not supported
+* Currently, storing GitLab Git LFS objects on a non-local storage (like S3 buckets)
+ is not supported
* Currently, removing LFS objects from GitLab Git LFS storage is not supported
+* LFS authentications via SSH is not supported for the time being
+* Only compatible with the GitLFS client versions 1.1.0 or 1.0.2.
diff --git a/doc/workflow/lfs/manage_large_binaries_with_git_lfs.md b/doc/workflow/lfs/manage_large_binaries_with_git_lfs.md
index b59e92cb317..ba91685a20b 100644
--- a/doc/workflow/lfs/manage_large_binaries_with_git_lfs.md
+++ b/doc/workflow/lfs/manage_large_binaries_with_git_lfs.md
@@ -1,17 +1,21 @@
# Git LFS
-Managing large files such as audio, video and graphics files has always been one of the shortcomings of Git.
-The general recommendation is to not have Git repositories larger than 1GB to preserve performance.
+Managing large files such as audio, video and graphics files has always been one
+of the shortcomings of Git. The general recommendation is to not have Git repositories
+larger than 1GB to preserve performance.
-GitLab already supports [managing large files with git annex](http://doc.gitlab.com/ee/workflow/git_annex.html) (EE only), however in certain
-environments it is not always convenient to use different commands to differentiate between the large files and regular ones.
+GitLab already supports [managing large files with git annex](http://doc.gitlab.com/ee/workflow/git_annex.html)
+(EE only), however in certain environments it is not always convenient to use
+different commands to differentiate between the large files and regular ones.
-Git LFS makes this simpler for the end user by removing the requirement to learn new commands.
+Git LFS makes this simpler for the end user by removing the requirement to
+learn new commands.
## How it works
-Git LFS client talks with the GitLab server over HTTPS. It uses HTTP Basic Authentication to authorize client requests.
-Once the request is authorized, Git LFS client receives instructions from where to fetch or where to push the large file.
+Git LFS client talks with the GitLab server over HTTPS. It uses HTTP Basic Authentication
+to authorize client requests. Once the request is authorized, Git LFS client receives
+instructions from where to fetch or where to push the large file.
## GitLab server configuration
@@ -24,15 +28,19 @@ Documentation for GitLab instance administrators is under [LFS administration do
## Known limitations
-* Git LFS v1 original API is not supported since it was deprecated early in LFS development
+* Git LFS v1 original API is not supported since it was deprecated early in LFS
+ development
* When SSH is set as a remote, Git LFS objects still go through HTTPS
-* Any Git LFS request will ask for HTTPS credentials to be provided so good Git credentials store is recommended
-* Git LFS always assumes HTTPS so if you have GitLab server on HTTP you will have to add the URL to Git config manually (see #troubleshooting)
+* Any Git LFS request will ask for HTTPS credentials to be provided so good Git
+ credentials store is recommended
+* Git LFS always assumes HTTPS so if you have GitLab server on HTTP you will have
+ to add the URL to Git config manually (see #troubleshooting)
## Using Git LFS
-Lets take a look at the workflow when you need to check large files into your Git repository with Git LFS:
-For example, if you want to upload a very large file and check it into your Git repository:
+Lets take a look at the workflow when you need to check large files into your Git
+repository with Git LFS. For example, if you want to upload a very large file and
+check it into your Git repository:
```bash
git clone git@gitlab.example.com:group/project.git
@@ -40,7 +48,8 @@ git lfs init # initialize the Git LFS project project
git lfs track "*.iso" # select the file extensions that you want to treat as large files
```
-Once a certain file extension is marked for tracking as a LFS object you can use Git as usual without having to redo the command to track a file with the same extension:
+Once a certain file extension is marked for tracking as a LFS object you can use
+Git as usual without having to redo the command to track a file with the same extension:
```bash
cp ~/tmp/debian.iso ./ # copy a large file into the current directory
@@ -49,13 +58,17 @@ git commit -am "Added Debian iso" # commit the file meta data
git push origin master # sync the git repo and large file to the GitLab server
```
-Cloning the repository works the same as before. Git automatically detects the LFS-tracked files and clones them via HTTP. If you performed the git clone command with a SSH URL, you have to enter your GitLab credentials for HTTP authentication.
+Cloning the repository works the same as before. Git automatically detects the
+LFS-tracked files and clones them via HTTP. If you performed the git clone
+command with a SSH URL, you have to enter your GitLab credentials for HTTP
+authentication.
```bash
git clone git@gitlab.example.com:group/project.git
```
-If you already cloned the repository and you want to get the latest LFS object that are on the remote repository, eg. from branch `master`:
+If you already cloned the repository and you want to get the latest LFS object
+that are on the remote repository, eg. from branch `master`:
```bash
git lfs fetch master
@@ -73,8 +86,8 @@ Check if you have permissions to push to the project or fetch from the project.
* Project is not allowed to access the LFS object
-LFS object you are trying to push to the project or fetch from the project is not available to the project anymore.
-Probably the object was removed from the server.
+LFS object you are trying to push to the project or fetch from the project is not
+available to the project anymore. Probably the object was removed from the server.
* Local git repository is using deprecated LFS API
@@ -89,16 +102,26 @@ git lfs logs last
If the status `error 501` is shown, it is because:
-* Git LFS support is not enabled on the GitLab server. Check with your GitLab administrator why Git LFS is not enabled on the server. See [LFS administration documentation](lfs_administration.md) for instructions on how to enable LFS support.
+* Git LFS support is not enabled on the GitLab server. Check with your GitLab
+ administrator why Git LFS is not enabled on the server. See
+ [LFS administration documentation](lfs_administration.md) for instructions
+ on how to enable LFS support.
-* Git LFS client version is not supported by GitLab server. Check your Git LFS version with `git lfs version`. Check the Git config of the project for traces of deprecated API with `git lfs -l`. If `batch = false` is set in the config, remove the line and try to update your Git LFS client. Only version 1.0.1 and newer are supported.
+* Git LFS client version is not supported by GitLab server. Check your Git LFS
+ version with `git lfs version`. Check the Git config of the project for traces
+ of deprecated API with `git lfs -l`. If `batch = false` is set in the config,
+ remove the line and try to update your Git LFS client. Only version 1.0.1 and
+ newer are supported.
### getsockopt: connection refused
-If you push a LFS object to a project and you receive an error similar to: `Post <URL>/info/lfs/objects/batch: dial tcp IP: getsockopt: connection refused`,
-the LFS client is trying to reach GitLab through HTTPS. However, your GitLab instance is being served on HTTP.
+If you push a LFS object to a project and you receive an error similar to:
+`Post <URL>/info/lfs/objects/batch: dial tcp IP: getsockopt: connection refused`,
+the LFS client is trying to reach GitLab through HTTPS. However, your GitLab
+instance is being served on HTTP.
-This behaviour is caused by Git LFS using HTTPS connections by default when a `lfsurl` is not set in the Git config.
+This behaviour is caused by Git LFS using HTTPS connections by default when a
+`lfsurl` is not set in the Git config.
To prevent this from happening, set the lfs url in project Git config:
@@ -109,18 +132,24 @@ git config --add lfs.url "http://gitlab.example.com/group/project.git/info/lfs/o
### Credentials are always required when pushing an object
-Given that Git LFS uses HTTP Basic Authentication to authenticate the user pushing the LFS object on every push for every object, user HTTPS credentials are required.
+Given that Git LFS uses HTTP Basic Authentication to authenticate the user pushing
+the LFS object on every push for every object, user HTTPS credentials are required.
-By default, Git has support for remembering the credentials for each repository you use. This is described in [Git credentials man pages](https://git-scm.com/docs/gitcredentials).
+By default, Git has support for remembering the credentials for each repository
+you use. This is described in [Git credentials man pages](https://git-scm.com/docs/gitcredentials).
-For example, you can tell Git to remember the password for a period of time in which you expect to push the objects:
+For example, you can tell Git to remember the password for a period of time in
+which you expect to push the objects:
```bash
git config --global credential.helper 'cache --timeout=3600'
```
-This will remember the credentials for an hour after which Git operations will require re-authentication.
+This will remember the credentials for an hour after which Git operations will
+require re-authentication.
-If you are using OS X you can use `osxkeychain` to store and encrypt your credentials. For Windows, you can use `wincred` or Microsoft's [Git Credential Manager for Windows](https://github.com/Microsoft/Git-Credential-Manager-for-Windows/releases).
+If you are using OS X you can use `osxkeychain` to store and encrypt your credentials.
+For Windows, you can use `wincred` or Microsoft's [Git Credential Manager for Windows](https://github.com/Microsoft/Git-Credential-Manager-for-Windows/releases).
-More details about various methods of storing the user credentials can be found on [Git Credential Storage documentation](https://git-scm.com/book/en/v2/Git-Tools-Credential-Storage). \ No newline at end of file
+More details about various methods of storing the user credentials can be found
+on [Git Credential Storage documentation](https://git-scm.com/book/en/v2/Git-Tools-Credential-Storage). \ No newline at end of file
diff --git a/doc/workflow/revert_changes.md b/doc/workflow/revert_changes.md
new file mode 100644
index 00000000000..399366b0cdc
--- /dev/null
+++ b/doc/workflow/revert_changes.md
@@ -0,0 +1,64 @@
+# Reverting changes
+
+_**Note:** This feature was [introduced][ce-1990] in GitLab 8.5._
+
+---
+
+GitLab implements Git's powerful feature to [revert any commit][git-revert]
+with introducing a **Revert** button in Merge Requests and commit details.
+
+## Reverting a Merge Request
+
+_**Note:** The **Revert** button will only be available for Merge Requests
+created since GitLab 8.5. However, you can still revert a Merge Request
+by reverting the merge commit from the list of Commits page._
+
+After the Merge Request has been merged, a **Revert** button will be available
+to revert the changes introduced by that Merge Request:
+
+![Revert Merge Request](img/revert_changes_mr.png)
+
+---
+
+You can revert the changes directly into the selected branch or you can opt to
+create a new Merge Request with the revert changes:
+
+![Revert Merge Request modal](img/revert_changes_mr_modal.png)
+
+---
+
+After the Merge Request has been reverted, the **Revert** button will not be
+available anymore.
+
+## Reverting a Commit
+
+You can revert a Commit from the Commit details page:
+
+![Revert commit](img/revert_changes_commit.png)
+
+---
+
+Similar to reverting a Merge Request, you can opt to revert the changes
+directly into the target branch or create a new Merge Request to revert the
+changes:
+
+![Revert commit modal](img/revert_changes_commit_modal.png)
+
+---
+
+After the Commit has been reverted, the **Revert** button will not be available
+anymore.
+
+Please note that when reverting merge commits, the mainline will always be the
+first parent. If you want to use a different mainline then you need to do that
+from the command line.
+
+Here is a quick example to revert a merge commit using the second parent as the
+mainline:
+
+```bash
+git revert -m 2 7a39eb0
+```
+
+[ce-1990]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/1990 "Revert button Merge Request"
+[git-revert]: https://git-scm.com/docs/git-revert "Git revert documentation"
diff --git a/doc/workflow/shortcuts.png b/doc/workflow/shortcuts.png
index e5914aa8e67..83e562d6929 100644
--- a/doc/workflow/shortcuts.png
+++ b/doc/workflow/shortcuts.png
Binary files differ
diff --git a/doc/workflow/todos.md b/doc/workflow/todos.md
new file mode 100644
index 00000000000..5f440fdafdd
--- /dev/null
+++ b/doc/workflow/todos.md
@@ -0,0 +1,73 @@
+# GitLab ToDos
+
+>**Note:** This feature was [introduced][ce-2817] in GitLab 8.5.
+
+When you log into GitLab, you normally want to see where you should spend your
+time and take some action, or what you need to keep an eye on. All without the
+mess of a huge pile of e-mail notifications. GitLab is where you do your work,
+so being able to get started quickly is very important.
+
+Todos is a chronological list of to-dos that are waiting for your input, all
+in a simple dashboard.
+
+![Todos screenshot showing a list of items to check on](img/todos_index.png)
+
+---
+
+You can access quickly your Todos dashboard by clicking the round gray icon
+next to the search bar in the upper right corner.
+
+![Todos icon](img/todos_icon.png)
+
+## What triggers a Todo
+
+A Todo appears in your Todos dashboard when:
+
+- an issue or merge request is assigned to you
+- you are `@mentioned` in an issue or merge request, be it the description of
+ the issue/merge request or in a comment
+
+>**Note:** Commenting on a commit will _not_ trigger a Todo.
+
+## How a Todo is marked as Done
+
+Any action to the corresponding issue or merge request will mark your Todo as
+**Done**. This action can include:
+
+- changing the assignee
+- changing the milestone
+- adding/removing a label
+- commenting on the issue
+
+In case where you think no action is needed, you can manually mark the todo as
+done by clicking the corresponding **Done** button, and it will disappear from
+your Todos list. If you want to mark all your Todos as done, just click on the
+**Mark all as done** button.
+
+---
+
+In order for a Todo to be marked as done, the action must be coming from you.
+So, if you close the related issue or merge the merge request yourself, and you
+had a Todo for that, it will automatically get marked as done. On the other
+hand, if someone else closes, merges or takes action on the issue or merge
+request, your Todo will remain pending. This makes sense because you may need
+to give attention to an issue even if it has been resolved.
+
+There is just one Todo per issue or merge request, so mentioning a user a
+hundred times in an issue will only trigger one Todo.
+
+## Filtering your Todos
+
+In general, there are four kinds of filters you can use on your Todos
+dashboard:
+
+| Filter | Description |
+| ------ | ----------- |
+| Project | Filter by project |
+| Author | Filter by the author that triggered the Todo |
+| Type | Filter by issue or merge request |
+| Action | Filter by the action that triggered the Todo (Assigned or Mentioned)|
+
+You can choose more than one filters at the same time.
+
+[ce-2817]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/2817
diff --git a/doc/workflow/web_editor.md b/doc/workflow/web_editor.md
index 7fc8f96b9ec..4a451d98953 100644
--- a/doc/workflow/web_editor.md
+++ b/doc/workflow/web_editor.md
@@ -1,26 +1,120 @@
# GitLab Web Editor
-In GitLab you can create new files and edit existing files using our web editor.
-This is especially useful if you don't have access to a command line or you just want to do a quick fix.
-You can easily access the web editor, depending on the context.
-Let's start from newly created project.
+Sometimes it's easier to make quick changes directly from the GitLab interface
+than to clone the project and use the Git command line tool. In this feature
+highlight we look at how you can create a new file, directory, branch or
+tag from the file browser. All of these actions are available from a single
+dropdown menu.
-Click on `Add a file`
-to create the first file and open it in the web editor.
+## Create a file
-![web editor 1](web_editor/empty_project.png)
+From a project's files page, click the '+' button to the right of the branch selector.
+Choose **New file** from the dropdown.
-Fill in a file name, some content, a commit message, branch name and press the commit button.
-The file will be saved to the repository.
+![New file dropdown menu](img/web_editor_new_file_dropdown.png)
-![web editor 2](web_editor/new_file.png)
+---
-You can edit any text file in a repository by pressing the edit button, when
-viewing the file.
+Enter a file name in the **File name** box. Then, add file content in the editor
+area. Add a descriptive commit message and choose a branch. The branch field
+will default to the branch you were viewing in the file browser. If you enter
+a new branch name, a checkbox will appear allowing you to start a new merge
+request after you commit the changes.
-![web editor 3](web_editor/show_file.png)
+When you are satisfied with your new file, click **Commit Changes** at the bottom.
-Editing a file is almost the same as creating a new file,
-with as addition the ability to preview your changes in a separate tab. Also you can save your change to another branch by filling out field `branch`
+![Create file editor](img/web_editor_new_file_editor.png)
-![web editor 3](web_editor/edit_file.png)
+## Upload a file
+
+The ability to create a file is great when the content is text. However, this
+doesn't work well for binary data such as images, PDFs or other file types. In
+this case you need to upload a file.
+
+From a project's files page, click the '+' button to the right of the branch
+selector. Choose **Upload file** from the dropdown.
+
+![Upload file dropdown menu](img/web_editor_upload_file_dropdown.png)
+
+---
+
+Once the upload dialog pops up there are two ways to upload your file. Either
+drag and drop a file on the pop up or use the **click to upload** link. A file
+preview will appear once you have selected a file to upload.
+
+Enter a commit message, choose a branch, and click **Upload file** when you are
+ready.
+
+![Upload file dialog](img/web_editor_upload_file_dialog.png)
+
+## Create a directory
+
+To keep files in the repository organized it is often helpful to create a new
+directory.
+
+From a project's files page, click the '+' button to the right of the branch selector.
+Choose **New directory** from the dropdown.
+
+![New directory dropdown](img/web_editor_new_directory_dropdown.png)
+
+---
+
+In the new directory dialog enter a directory name, a commit message and choose
+the target branch. Click **Create directory** to finish.
+
+![New directory dialog](img/web_editor_new_directory_dialog.png)
+
+## Create a new branch
+
+If you want to make changes to several files before creating a new merge
+request, you can create a new branch up front. From a project's files page,
+choose **New branch** from the dropdown.
+
+![New branch dropdown](img/web_editor_new_branch_dropdown.png)
+
+---
+
+Enter a new **Branch name**. Optionally, change the **Create from** field
+to choose which branch, tag or commit SHA this new branch will originate from.
+This field will autocomplete if you start typing an existing branch or tag.
+Click **Create branch** and you will be returned to the file browser on this new
+branch.
+
+![New branch page](img/web_editor_new_branch_page.png)
+
+---
+
+You can now make changes to any files, as needed. When you're ready to merge
+the changes back to master you can use the widget at the top of the screen.
+This widget only appears for a period of time after you create the branch or
+modify files.
+
+![New push widget](img/web_editor_new_push_widget.png)
+
+## Create a new tag
+
+Tags are useful for marking major milestones such as production releases,
+release candidates, and more. You can create a tag from a branch or a commit
+SHA. From a project's files page, choose **New tag** from the dropdown.
+
+![New tag dropdown](img/web_editor_new_tag_dropdown.png)
+
+---
+
+Give the tag a name such as `v1.0.0`. Choose the branch or SHA from which you
+would like to create this new tag. You can optionally add a message and
+release notes. The release notes section supports markdown format and you can
+also upload an attachment. Click **Create tag** and you will be taken to the tag
+list page.
+
+![New tag page](img/web_editor_new_tag_page.png)
+
+## Tips
+
+When creating or uploading a new file, or creating a new directory, you can
+trigger a new merge request rather than committing directly to master. Enter
+a new branch name in the **Target branch** field. You will notice a checkbox
+appear that is labeled **Start a new merge request with these changes**. After
+you commit the changes you will be taken to a new merge request form.
+
+![Start a new merge request with these changes](img/web_editor_start_new_merge_request.png)
diff --git a/doc/workflow/web_editor/edit_file.png b/doc/workflow/web_editor/edit_file.png
deleted file mode 100644
index f480c69ac3e..00000000000
--- a/doc/workflow/web_editor/edit_file.png
+++ /dev/null
Binary files differ
diff --git a/doc/workflow/web_editor/empty_project.png b/doc/workflow/web_editor/empty_project.png
deleted file mode 100644
index 6a049f6beaf..00000000000
--- a/doc/workflow/web_editor/empty_project.png
+++ /dev/null
Binary files differ
diff --git a/doc/workflow/web_editor/new_file.png b/doc/workflow/web_editor/new_file.png
deleted file mode 100644
index 55ebd9e0257..00000000000
--- a/doc/workflow/web_editor/new_file.png
+++ /dev/null
Binary files differ
diff --git a/doc/workflow/web_editor/show_file.png b/doc/workflow/web_editor/show_file.png
deleted file mode 100644
index 9cafcb55109..00000000000
--- a/doc/workflow/web_editor/show_file.png
+++ /dev/null
Binary files differ
diff --git a/features/admin/appearance.feature b/features/admin/appearance.feature
new file mode 100644
index 00000000000..5c1dd7531c1
--- /dev/null
+++ b/features/admin/appearance.feature
@@ -0,0 +1,37 @@
+Feature: Admin Appearance
+ Scenario: Create new appearance
+ Given I sign in as an admin
+ And I visit admin appearance page
+ When submit form with new appearance
+ Then I should be redirected to admin appearance page
+ And I should see newly created appearance
+
+ Scenario: Preview appearance
+ Given application has custom appearance
+ And I sign in as an admin
+ When I visit admin appearance page
+ And I click preview button
+ Then I should see a customized appearance
+
+ Scenario: Custom sign-in page
+ Given application has custom appearance
+ When I visit login page
+ Then I should see a customized appearance
+
+ Scenario: Appearance logo
+ Given application has custom appearance
+ And I sign in as an admin
+ And I visit admin appearance page
+ When I attach a logo
+ Then I should see a logo
+ And I remove the logo
+ Then I should see logo removed
+
+ Scenario: Header logos
+ Given application has custom appearance
+ And I sign in as an admin
+ And I visit admin appearance page
+ When I attach header logos
+ Then I should see header logos
+ And I remove the header logos
+ Then I should see header logos removed
diff --git a/features/dashboard/event_filters.feature b/features/dashboard/event_filters.feature
index 96399ea21a6..8c3ff64164f 100644
--- a/features/dashboard/event_filters.feature
+++ b/features/dashboard/event_filters.feature
@@ -43,10 +43,16 @@ Feature: Event Filters
And I should not see new member event
When I click "team" event filter
And I visit dashboard activity page
- Then I should see push event
+ Then I should not see push event
And I should see new member event
And I should not see merge request event
When I click "push" event filter
- Then I should not see push event
- And I should see new member event
+ And I visit dashboard activity page
+ Then I should see push event
+ And I should not see new member event
And I should not see merge request event
+ When I click "merge" event filter
+ And I visit dashboard activity page
+ Then I should see merge request event
+ And I should not see push event
+ And I should not see new member event
diff --git a/features/dashboard/todos.feature b/features/dashboard/todos.feature
new file mode 100644
index 00000000000..1e7b1b50d64
--- /dev/null
+++ b/features/dashboard/todos.feature
@@ -0,0 +1,38 @@
+@dashboard
+Feature: Dashboard Todos
+ Background:
+ Given I sign in as a user
+ And I own project "Shop"
+ And "John Doe" is a developer of project "Shop"
+ And "Mary Jane" is a developer of project "Shop"
+ And "Mary Jane" owns private project "Enterprise"
+ And I am a developer of project "Enterprise"
+ And I have todos
+ And I visit dashboard todos page
+
+ @javascript
+ Scenario: I mark todos as done
+ Then I should see todos assigned to me
+ And I mark the todo as done
+ And I click on the "Done" tab
+ Then I should see all todos marked as done
+
+ @javascript
+ Scenario: I filter by project
+ Given I filter by "Enterprise"
+ Then I should not see todos
+
+ @javascript
+ Scenario: I filter by author
+ Given I filter by "John Doe"
+ Then I should not see todos related to "Mary Jane" in the list
+
+ @javascript
+ Scenario: I filter by type
+ Given I filter by "Issue"
+ Then I should not see todos related to "Merge Requests" in the list
+
+ @javascript
+ Scenario: I filter by action
+ Given I filter by "Mentioned"
+ Then I should not see todos related to "Assignments" in the list
diff --git a/features/explore/projects.feature b/features/explore/projects.feature
index 629859e960d..7df6b6f09ba 100644
--- a/features/explore/projects.feature
+++ b/features/explore/projects.feature
@@ -87,6 +87,7 @@ Feature: Explore Projects
Scenario: I visit public project issues page as a non authorized user
Given I visit project "Community" page
+ Then I should not see command line instructions
And I visit "Community" issues page
Then I should see list of issues for "Community" project
diff --git a/features/group/milestones.feature b/features/group/milestones.feature
index 62ea66a783c..d6c05df9840 100644
--- a/features/group/milestones.feature
+++ b/features/group/milestones.feature
@@ -28,3 +28,20 @@ Feature: Group Milestones
And I fill milestone name
When I press create mileston button
Then milestone in each project should be created
+
+ Scenario: I should see Issues listed with labels
+ Given Group has projects with milestones
+ When I visit group "Owned" page
+ And I click on group milestones
+ And I click on one group milestone
+ Then I should see the "bug" label
+ And I should see the "feature" label
+ And I should see the project name in the Issue row
+
+ Scenario: I should see the Labels tab
+ Given Group has projects with milestones
+ When I visit group "Owned" page
+ And I click on group milestones
+ And I click on one group milestone
+ And I click on the "Labels" tab
+ Then I should see the list of labels
diff --git a/features/groups.feature b/features/groups.feature
index 55fffb012ae..a60c3860b83 100644
--- a/features/groups.feature
+++ b/features/groups.feature
@@ -22,11 +22,23 @@ Feature: Groups
When I visit group "Owned" issues page
Then I should see issues from group "Owned" assigned to me
+ Scenario: I should not see issues from archived project in "Owned" group issues list
+ Given Group "Owned" has archived project
+ And the archived project have some issues
+ When I visit group "Owned" issues page
+ Then I should not see issues from the archived project
+
Scenario: I should see group "Owned" merge requests list
Given project from group "Owned" has merge requests assigned to me
When I visit group "Owned" merge requests page
Then I should see merge requests from group "Owned" assigned to me
+ Scenario: I should not see merge requests from archived project in "Owned" group merge requests list
+ Given Group "Owned" has archived project
+ And the archived project have some merge_requests
+ When I visit group "Owned" merge requests page
+ Then I should not see merge requests from the archived project
+
Scenario: I should see edit group "Owned" page
When I visit group "Owned" settings page
And I change group "Owned" name to "new-name"
diff --git a/features/login_form.feature b/features/login_form.feature
deleted file mode 100644
index b4d95754482..00000000000
--- a/features/login_form.feature
+++ /dev/null
@@ -1,5 +0,0 @@
-Feature: Login form
- Scenario: I see crowd form
- Given Crowd integration enabled
- When I visit sign in page
- Then I should see Crowd login form \ No newline at end of file
diff --git a/features/profile/ssh_keys.feature b/features/profile/ssh_keys.feature
index 581503fc5f9..b0d5b748916 100644
--- a/features/profile/ssh_keys.feature
+++ b/features/profile/ssh_keys.feature
@@ -9,7 +9,7 @@ Feature: Profile SSH Keys
Then I should see my ssh keys
Scenario: Add new ssh key
- Given I click link "Add new"
+ Given I should see new ssh key form
And I submit new ssh key "Laptop"
Then I should see new ssh key "Laptop"
diff --git a/features/project/badges/build.feature b/features/project/badges/build.feature
index 9417f62d680..bcf80ed620e 100644
--- a/features/project/badges/build.feature
+++ b/features/project/badges/build.feature
@@ -20,3 +20,8 @@ Feature: Project Badges Build
And project has another build that is running
When I display builds badge for a master branch
Then I should see a build running badge
+
+ Scenario: I want to see a fresh badge on each request
+ Given recent build is successful
+ When I display builds badge for a master branch
+ Then I should see a badge that has not been cached
diff --git a/features/project/builds/summary.feature b/features/project/builds/summary.feature
index b69d279517b..3c029a973df 100644
--- a/features/project/builds/summary.feature
+++ b/features/project/builds/summary.feature
@@ -3,6 +3,7 @@ Feature: Project Builds Summary
Given I sign in as a user
And I own a project
And project has CI enabled
+ And project has coverage enabled
And project has a recent build
Scenario: I browse build details page
@@ -12,4 +13,14 @@ Feature: Project Builds Summary
Scenario: I browse project builds page
When I visit project builds page
+ Then I see coverage
Then I see button to CI Lint
+
+ Scenario: I erase a build
+ Given recent build is successful
+ And recent build has a build trace
+ When I visit recent build details page
+ And I click erase build button
+ Then recent build has been erased
+ And recent build summary does not have artifacts widget
+ And recent build summary contains information saying that build has been erased
diff --git a/features/project/commits/revert.feature b/features/project/commits/revert.feature
new file mode 100644
index 00000000000..7a2effafe03
--- /dev/null
+++ b/features/project/commits/revert.feature
@@ -0,0 +1,28 @@
+@project_commits
+Feature: Revert Commits
+ Background:
+ Given I sign in as a user
+ And I own a project
+ And I visit my project's commits page
+
+ Scenario: I revert a commit
+ Given I click on commit link
+ And I click on the revert button
+ And I revert the changes directly
+ Then I should see the revert commit notice
+
+ Scenario: I revert a commit that was previously reverted
+ Given I click on commit link
+ And I click on the revert button
+ And I revert the changes directly
+ And I visit my project's commits page
+ And I click on commit link
+ And I click on the revert button
+ And I revert the changes directly
+ Then I should see a revert error
+
+ Scenario: I revert a commit in a new merge request
+ Given I click on commit link
+ And I click on the revert button
+ And I revert the changes in a new merge request
+ Then I should see the new merge request notice
diff --git a/features/project/fork.feature b/features/project/fork.feature
index 12695204e47..ca3f2771aa5 100644
--- a/features/project/fork.feature
+++ b/features/project/fork.feature
@@ -32,6 +32,13 @@ Feature: Project Fork
And I visit the forks page of the "Shop" project
Then I should see my fork on the list
+ Scenario: Viewing forks of a Project that has no repo
+ Given I click link "Fork"
+ When I fork to my namespace
+ And I make forked repo invalid
+ And I visit the forks page of the "Shop" project
+ Then I should see my fork on the list
+
Scenario: Viewing private forks of a Project
Given There is an existent fork of the "Shop" project
And I click link "Fork"
diff --git a/features/project/issues/award_emoji.feature b/features/project/issues/award_emoji.feature
index bfde89fd896..2945bb3753a 100644
--- a/features/project/issues/award_emoji.feature
+++ b/features/project/issues/award_emoji.feature
@@ -7,7 +7,16 @@ Feature: Award Emoji
And I visit "Bugfix" issue page
@javascript
- Scenario: I add and remove award in the issue
+ Scenario: I repeatedly add and remove thumbsup award in the issue
+ Given I click the thumbsup award Emoji
+ Then I have award added
+ Given I click the thumbsup award Emoji
+ Then I have no awards added
+ Given I click the thumbsup award Emoji
+ Then I have award added
+
+ @javascript
+ Scenario: I add and remove custom award in the issue
Given I click to emoji-picker
Then The search field is focused
And I click to emoji in the picker
diff --git a/features/project/issues/issues.feature b/features/project/issues/issues.feature
index 0b3d03aa2a5..ff21c7d1b83 100644
--- a/features/project/issues/issues.feature
+++ b/features/project/issues/issues.feature
@@ -25,9 +25,16 @@ Feature: Project Issues
Scenario: I visit issue page
Given I click link "Release 0.4"
Then I should see issue "Release 0.4"
+ And I should see "1 of 2" in the sidebar
+
+ Scenario: I navigate between issues
+ Given I click link "Release 0.4"
+ Then I click link "Next" in the sidebar
+ Then I should see issue "Tweet control"
+ And I should see "2 of 2" in the sidebar
@javascript
- Scenario: I visit issue page
+ Scenario: I filter by author
Given I add a user to project "Shop"
And I click "author" dropdown
Then I see current user as the first user
@@ -52,14 +59,6 @@ Feature: Project Issues
And I should see an error alert section within the comment form
@javascript
- Scenario: Visiting Issues after leaving a comment
- Given I visit issue page "Release 0.4"
- And I leave a comment like "XML attached"
- And I visit project "Shop" issues page
- And I sort the list by "Last updated"
- Then I should see "Release 0.4" at the top
-
- @javascript
Scenario: Visiting Issues after being sorted the list
Given I visit project "Shop" issues page
And I sort the list by "Oldest updated"
@@ -82,6 +81,16 @@ Feature: Project Issues
Then The list should be sorted by "Oldest updated"
@javascript
+ Scenario: Sort issues by upvotes/downvotes
+ Given project "Shop" have "Bugfix" open issue
+ And issue "Release 0.4" have 2 upvotes and 1 downvote
+ And issue "Tweet control" have 1 upvote and 2 downvotes
+ And I sort the list by "Most popular"
+ Then The list should be sorted by "Most popular"
+ And I sort the list by "Least popular"
+ Then The list should be sorted by "Least popular"
+
+ @javascript
Scenario: I search issue
Given I fill in issue search with "Re"
Then I should see "Release 0.4" in issues
diff --git a/features/project/merge_requests.feature b/features/project/merge_requests.feature
index ca1ee6b3c2b..f8d9fe1854d 100644
--- a/features/project/merge_requests.feature
+++ b/features/project/merge_requests.feature
@@ -26,6 +26,16 @@ Feature: Project Merge Requests
When I visit project "Shop" merge requests page
Then I should see "other_branch" branch
+ Scenario: I should not see the numbers of diverged commits if the branch is rebased on the target
+ Given project "Shop" have "Bug NS-07" open merge request with rebased branch
+ When I visit merge request page "Bug NS-07"
+ Then I should not see the diverged commits count
+
+ Scenario: I should see the numbers of diverged commits if the branch diverged from the target
+ Given project "Shop" have "Bug NS-08" open merge request with diverged branch
+ When I visit merge request page "Bug NS-08"
+ Then I should see the diverged commits count
+
Scenario: I should see rejected merge requests
Given I click link "Closed"
Then I should see "Feature NS-03" in merge requests
@@ -39,6 +49,7 @@ Feature: Project Merge Requests
Scenario: I visit merge request page
Given I click link "Bug NS-04"
Then I should see merge request "Bug NS-04"
+ And I should see "1 of 1" in the sidebar
Scenario: I close merge request page
Given I click link "Bug NS-04"
@@ -76,15 +87,6 @@ Feature: Project Merge Requests
Then I should see comment "XML attached"
@javascript
- Scenario: Visiting Merge Requests after leaving a comment
- Given project "Shop" have "Bug NS-05" open merge request with diffs inside
- And I visit merge request page "Bug NS-04"
- And I leave a comment like "XML attached"
- And I visit project "Shop" merge requests page
- And I sort the list by "Last updated"
- Then I should see "Bug NS-04" at the top
-
- @javascript
Scenario: Visiting Merge Requests after being sorted the list
Given I visit project "Shop" merge requests page
And I sort the list by "Oldest updated"
@@ -107,14 +109,15 @@ Feature: Project Merge Requests
Then The list should be sorted by "Oldest updated"
@javascript
- Scenario: Visiting Merge Requests after commenting on diffs
+ Scenario: Sort merge requests by upvotes/downvotes
Given project "Shop" have "Bug NS-05" open merge request with diffs inside
- And I visit merge request page "Bug NS-05"
- And I click on the Changes tab
- And I leave a comment like "Line is wrong" on diff
- And I visit project "Shop" merge requests page
- And I sort the list by "Last updated"
- Then I should see "Bug NS-05" at the top
+ And project "Shop" have "Bug NS-06" open merge request
+ And merge request "Bug NS-04" have 2 upvotes and 1 downvote
+ And merge request "Bug NS-06" have 1 upvote and 2 downvotes
+ And I sort the list by "Most popular"
+ Then The list should be sorted by "Most popular"
+ And I sort the list by "Least popular"
+ Then The list should be sorted by "Least popular"
@javascript
Scenario: I comment on a merge request diff
diff --git a/features/project/merge_requests/revert.feature b/features/project/merge_requests/revert.feature
new file mode 100644
index 00000000000..d767b088883
--- /dev/null
+++ b/features/project/merge_requests/revert.feature
@@ -0,0 +1,30 @@
+@project_merge_requests
+Feature: Revert Merge Requests
+ Background:
+ Given There is an open Merge Request
+ And I am signed in as a developer of the project
+ And I am on the Merge Request detail page
+ And I click on Accept Merge Request
+
+ @javascript
+ Scenario: I revert a merge request
+ Given I click on the revert button
+ And I revert the changes directly
+ Then I should see the revert merge request notice
+
+ @javascript
+ Scenario: I revert a merge request that was previously reverted
+ Given I click on the revert button
+ And I revert the changes directly
+ And I am on the Merge Request detail page
+ And I click on the revert button
+ And I revert the changes directly
+ Then I should see a revert error
+
+ @javascript
+ Scenario: I revert a merge request in a new merge request
+ Given I click on the revert button
+ And I am on the Merge Request detail page
+ And I click on the revert button
+ And I revert the changes in a new merge request
+ Then I should see the new merge request notice
diff --git a/features/project/milestone.feature b/features/project/milestone.feature
new file mode 100644
index 00000000000..713f0f3b979
--- /dev/null
+++ b/features/project/milestone.feature
@@ -0,0 +1,24 @@
+Feature: Project Milestone
+ Background:
+ Given I sign in as a user
+ And I own project "Shop"
+ And project "Shop" has labels: "bug", "feature", "enhancement"
+ And project "Shop" has milestone "v2.2"
+ And milestone has issue "Bugfix1" with labels: "bug", "feature"
+ And milestone has issue "Bugfix2" with labels: "bug", "enhancement"
+
+
+ @javascript
+ Scenario: Listing issues from issues tab
+ Given I visit project "Shop" milestones page
+ And I click link "v2.2"
+ Then I should see the labels "bug", "enhancement" and "feature"
+ And I should see the "bug" label listed only once
+
+ @javascript
+ Scenario: Listing labels from labels tab
+ Given I visit project "Shop" milestones page
+ And I click link "v2.2"
+ And I click link "Labels"
+ Then I should see the list of labels
+ And I should see the labels "bug", "enhancement" and "feature"
diff --git a/features/project/project.feature b/features/project/project.feature
index 1a53945eb04..f1f3ed26065 100644
--- a/features/project/project.feature
+++ b/features/project/project.feature
@@ -86,3 +86,9 @@ Feature: Project
Given I click notifications drop down button
When I choose Mention setting
Then I should see Notification saved message
+
+ Scenario: I should see command line instructions
+ Given I own an empty project
+ And I visit my empty project page
+ And I create bare repo
+ Then I should see command line instructions
diff --git a/features/search.feature b/features/search.feature
index a9234c1a611..3cd52810e59 100644
--- a/features/search.feature
+++ b/features/search.feature
@@ -65,3 +65,25 @@ Feature: Search
And I search for "Wiki content"
And I click "Wiki" link
Then I should see "test_wiki" link in the search results
+
+ Scenario: I logout and should see project I am looking for
+ Given project "Shop" is public
+ And I logout
+ And I search for "Sho"
+ Then I should see "Shop" project link
+
+ Scenario: I logout and should see issues I am looking for
+ Given project "Shop" is public
+ And I logout
+ And project has issues
+ When I search for "Foo"
+ And I click "Issues" link
+ Then I should see "Foo" link in the search results
+ And I should not see "Bar" link in the search results
+
+ Scenario: I logout and should see project code I am looking for
+ Given project "Shop" is public
+ And I logout
+ When I visit project "Shop" page
+ And I search for "rspec" on project page
+ Then I should see code results for project "Shop"
diff --git a/features/steps/admin/appearance.rb b/features/steps/admin/appearance.rb
new file mode 100644
index 00000000000..0d1be46d11d
--- /dev/null
+++ b/features/steps/admin/appearance.rb
@@ -0,0 +1,72 @@
+class Spinach::Features::AdminAppearance < Spinach::FeatureSteps
+ include SharedAuthentication
+ include SharedPaths
+
+ step 'submit form with new appearance' do
+ fill_in 'appearance_title', with: 'MyCompany'
+ fill_in 'appearance_description', with: 'dev server'
+ click_button 'Save'
+ end
+
+ step 'I should be redirected to admin appearance page' do
+ expect(current_path).to eq admin_appearances_path
+ expect(page).to have_content 'Appearance settings'
+ end
+
+ step 'I should see newly created appearance' do
+ expect(page).to have_field('appearance_title', with: 'MyCompany')
+ expect(page).to have_field('appearance_description', with: 'dev server')
+ expect(page).to have_content 'Last edit'
+ end
+
+ step 'I click preview button' do
+ click_link "Preview"
+ end
+
+ step 'application has custom appearance' do
+ create(:appearance)
+ end
+
+ step 'I should see a customized appearance' do
+ expect(page).to have_content appearance.title
+ expect(page).to have_content appearance.description
+ end
+
+ step 'I attach a logo' do
+ attach_file(:appearance_logo, Rails.root.join('spec', 'fixtures', 'dk.png'))
+ click_button 'Save'
+ end
+
+ step 'I attach header logos' do
+ attach_file(:appearance_header_logo, Rails.root.join('spec', 'fixtures', 'dk.png'))
+ click_button 'Save'
+ end
+
+ step 'I should see a logo' do
+ expect(page).to have_xpath('//img[@src="/uploads/appearance/logo/1/dk.png"]')
+ end
+
+ step 'I should see header logos' do
+ expect(page).to have_xpath('//img[@src="/uploads/appearance/header_logo/1/dk.png"]')
+ end
+
+ step 'I remove the logo' do
+ click_link 'Remove logo'
+ end
+
+ step 'I remove the header logos' do
+ click_link 'Remove header logo'
+ end
+
+ step 'I should see logo removed' do
+ expect(page).not_to have_xpath('//img[@src="/uploads/appearance/logo/1/gitlab_logo.png"]')
+ end
+
+ step 'I should see header logos removed' do
+ expect(page).not_to have_xpath('//img[@src="/uploads/appearance/header_logo/1/header_logo_light.png"]')
+ end
+
+ def appearance
+ Appearance.last
+ end
+end
diff --git a/features/steps/dashboard/todos.rb b/features/steps/dashboard/todos.rb
new file mode 100644
index 00000000000..9722a5a848c
--- /dev/null
+++ b/features/steps/dashboard/todos.rb
@@ -0,0 +1,128 @@
+class Spinach::Features::DashboardTodos < Spinach::FeatureSteps
+ include SharedAuthentication
+ include SharedPaths
+ include SharedProject
+ include SharedUser
+ include Select2Helper
+
+ step '"John Doe" is a developer of project "Shop"' do
+ project.team << [john_doe, :developer]
+ end
+
+ step 'I am a developer of project "Enterprise"' do
+ enterprise.team << [current_user, :developer]
+ end
+
+ step '"Mary Jane" is a developer of project "Shop"' do
+ project.team << [john_doe, :developer]
+ end
+
+ step 'I have todos' do
+ create(:todo, user: current_user, project: project, author: mary_jane, target: issue, action: Todo::MENTIONED)
+ create(:todo, user: current_user, project: project, author: john_doe, target: issue, action: Todo::ASSIGNED)
+ note = create(:note, author: john_doe, noteable: issue, note: "#{current_user.to_reference} Wdyt?")
+ create(:todo, user: current_user, project: project, author: john_doe, target: issue, action: Todo::MENTIONED, note: note)
+ create(:todo, user: current_user, project: project, author: john_doe, target: merge_request, action: Todo::ASSIGNED)
+ end
+
+ step 'I should see todos assigned to me' do
+ expect(page).to have_content 'To do 4'
+ expect(page).to have_content 'Done 0'
+
+ expect(page).to have_link project.name_with_namespace
+ should_see_todo(1, "John Doe assigned you merge request !#{merge_request.iid}", merge_request.title)
+ should_see_todo(2, "John Doe mentioned you on issue ##{issue.iid}", "#{current_user.to_reference} Wdyt?")
+ should_see_todo(3, "John Doe assigned you issue ##{issue.iid}", issue.title)
+ should_see_todo(4, "Mary Jane mentioned you on issue ##{issue.iid}", issue.title)
+ end
+
+ step 'I mark the todo as done' do
+ page.within('.todo:nth-child(1)') do
+ click_link 'Done'
+ end
+
+ expect(page).to have_content 'Todo was successfully marked as done.'
+ expect(page).to have_content 'To do 3'
+ expect(page).to have_content 'Done 1'
+ should_not_see_todo "John Doe assigned you merge request !#{merge_request.iid}"
+ end
+
+ step 'I click on the "Done" tab' do
+ click_link 'Done 1'
+ end
+
+ step 'I should see all todos marked as done' do
+ expect(page).to have_link project.name_with_namespace
+ should_see_todo(1, "John Doe assigned you merge request !#{merge_request.iid}", merge_request.title, false)
+ end
+
+ step 'I filter by "Enterprise"' do
+ select2(enterprise.id, from: "#project_id")
+ end
+
+ step 'I filter by "John Doe"' do
+ select2(john_doe.id, from: "#author_id")
+ end
+
+ step 'I filter by "Issue"' do
+ select2('Issue', from: "#type")
+ end
+
+ step 'I filter by "Mentioned"' do
+ select2("#{Todo::MENTIONED}", from: '#action_id')
+ end
+
+ step 'I should not see todos' do
+ expect(page).to have_content "You're all done!"
+ end
+
+ step 'I should not see todos related to "Mary Jane" in the list' do
+ should_not_see_todo "Mary Jane mentioned you on issue ##{issue.iid}"
+ end
+
+ step 'I should not see todos related to "Merge Requests" in the list' do
+ should_not_see_todo "John Doe assigned you merge request !#{merge_request.iid}"
+ end
+
+ step 'I should not see todos related to "Assignments" in the list' do
+ should_not_see_todo "John Doe assigned you merge request !#{merge_request.iid}"
+ should_not_see_todo "John Doe assigned you issue ##{issue.iid}"
+ end
+
+ def should_see_todo(position, title, body, pending = true)
+ page.within(".todo:nth-child(#{position})") do
+ expect(page).to have_content title
+ expect(page).to have_content body
+
+ if pending
+ expect(page).to have_link 'Done'
+ else
+ expect(page).to_not have_link 'Done'
+ end
+ end
+ end
+
+ def should_not_see_todo(title)
+ expect(page).not_to have_content title
+ end
+
+ def john_doe
+ @john_doe ||= user_exists("John Doe", { username: "john_doe" })
+ end
+
+ def mary_jane
+ @mary_jane ||= user_exists("Mary Jane", { username: "mary_jane" })
+ end
+
+ def enterprise
+ @enterprise ||= Project.find_by(name: 'Enterprise')
+ end
+
+ def issue
+ @issue ||= create(:issue, assignee: current_user, project: project)
+ end
+
+ def merge_request
+ @merge_request ||= create(:merge_request, assignee: current_user, source_project: project)
+ end
+end
diff --git a/features/steps/explore/projects.rb b/features/steps/explore/projects.rb
index 742ba5d71f6..cb6fa8a47da 100644
--- a/features/steps/explore/projects.rb
+++ b/features/steps/explore/projects.rb
@@ -18,7 +18,7 @@ class Spinach::Features::ExploreProjects < Spinach::FeatureSteps
end
step 'I should see empty public project details' do
- expect(page).to have_content 'Git global setup'
+ expect(page).not_to have_content 'Git global setup'
end
step 'I should see empty public project details with http clone info' do
diff --git a/features/steps/group/milestones.rb b/features/steps/group/milestones.rb
index 2363ad797fa..a167d259837 100644
--- a/features/steps/group/milestones.rb
+++ b/features/steps/group/milestones.rb
@@ -24,6 +24,9 @@ class Spinach::Features::GroupMilestones < Spinach::FeatureSteps
end
step 'I click on one group milestone' do
+ milestones = Milestone.where(title: 'GL-113')
+ @global_milestone = GlobalMilestone.new('GL-113', milestones)
+
click_link 'GL-113'
end
@@ -33,7 +36,7 @@ class Spinach::Features::GroupMilestones < Spinach::FeatureSteps
step 'I should see group milestone with all issues and MRs assigned to that milestone' do
expect(page).to have_content('Milestone GL-113')
- expect(page).to have_content('Progress: 0 closed – 3 open')
+ expect(page).to have_content('3 issues: 3 open and 0 closed')
issue = Milestone.find_by(name: 'GL-113').issues.first
expect(page).to have_link(issue.title, href: namespace_project_issue_path(issue.project.namespace, issue.project, issue))
end
@@ -60,6 +63,39 @@ class Spinach::Features::GroupMilestones < Spinach::FeatureSteps
end
end
+ step 'I should see the "bug" label' do
+ page.within('#tab-issues') do
+ expect(page).to have_content 'bug'
+ end
+ end
+
+ step 'I should see the "feature" label' do
+ page.within('#tab-issues') do
+ expect(page).to have_content 'bug'
+ end
+ end
+
+ step 'I should see the project name in the Issue row' do
+ page.within('#tab-issues') do
+ @global_milestone.projects.each do |project|
+ expect(page).to have_content project.name
+ end
+ end
+ end
+
+ step 'I click on the "Labels" tab' do
+ page.within('.nav-links') do
+ page.find(:xpath, "//a[@href='#tab-labels']").click
+ end
+ end
+
+ step 'I should see the list of labels' do
+ page.within('#tab-labels') do
+ expect(page).to have_content 'bug'
+ expect(page).to have_content 'feature'
+ end
+ end
+
private
def group_milestone
@@ -68,6 +104,10 @@ class Spinach::Features::GroupMilestones < Spinach::FeatureSteps
%w(gitlabhq gitlab-ci cookbook-gitlab).each do |path|
project = create :project, path: path, group: group
milestone = create :milestone, title: "Version 7.2", project: project
+
+ create(:label, project: project, title: 'bug')
+ create(:label, project: project, title: 'feature')
+
create :issue,
project: project,
assignee: current_user,
@@ -80,11 +120,14 @@ class Spinach::Features::GroupMilestones < Spinach::FeatureSteps
due_date: '2114-08-20',
description: 'Lorem Ipsum is simply dummy text'
- create :issue,
+ issue = create :issue,
project: project,
assignee: current_user,
author: current_user,
milestone: milestone
+
+ issue.labels << project.labels.find_by(title: 'bug')
+ issue.labels << project.labels.find_by(title: 'feature')
end
end
end
diff --git a/features/steps/groups.rb b/features/steps/groups.rb
index 1e2a78a6029..7a6ae15ffa5 100644
--- a/features/steps/groups.rb
+++ b/features/steps/groups.rb
@@ -44,6 +44,18 @@ class Spinach::Features::Groups < Spinach::FeatureSteps
end
end
+ step 'I should not see issues from the archived project' do
+ @archived_project.issues.each do |issue|
+ expect(page).not_to have_content issue.title
+ end
+ end
+
+ step 'I should not see merge requests from the archived project' do
+ @archived_project.merge_requests.each do |mr|
+ expect(page).not_to have_content mr.title
+ end
+ end
+
step 'I should see merge requests from group "Owned" assigned to me' do
assigned_to_me(:merge_requests).each do |issue|
expect(page).to have_content issue.title[0..80]
@@ -113,7 +125,7 @@ class Spinach::Features::Groups < Spinach::FeatureSteps
step 'Group "Owned" has archived project' do
group = Group.find_by(name: 'Owned')
- create(:project, namespace: group, archived: true, path: "archived-project")
+ @archived_project = create(:project, namespace: group, archived: true, path: "archived-project")
end
step 'I should see "archived" label' do
@@ -124,6 +136,21 @@ class Spinach::Features::Groups < Spinach::FeatureSteps
visit group_path(-1)
end
+ step 'the archived project have some issues' do
+ create :issue,
+ project: @archived_project,
+ assignee: current_user,
+ author: current_user
+ end
+
+ step 'the archived project have some merge requests' do
+ create :merge_request,
+ source_project: @archived_project,
+ target_project: @archived_project,
+ assignee: current_user,
+ author: current_user
+ end
+
private
def assigned_to_me(key)
diff --git a/features/steps/login_form.rb b/features/steps/login_form.rb
deleted file mode 100644
index b9ff6ae67fd..00000000000
--- a/features/steps/login_form.rb
+++ /dev/null
@@ -1,25 +0,0 @@
-class Spinach::Features::LoginForm < Spinach::FeatureSteps
- include SharedAuthentication
- include SharedPaths
- include SharedSnippet
- include SharedUser
- include SharedSearch
-
- step 'Crowd integration enabled' do
- @providers_orig = Gitlab::OAuth::Provider.providers
- @omniauth_conf_orig = Gitlab.config.omniauth.enabled
- expect(Gitlab::OAuth::Provider).to receive(:providers).and_return([:crowd])
- allow_any_instance_of(ApplicationHelper).to receive(:user_omniauth_authorize_path).and_return(root_path)
- expect(Gitlab.config.omniauth).to receive(:enabled).and_return(true)
- end
-
- step 'I should see Crowd login form' do
- expect(page).to have_selector '#tab-crowd form'
- Gitlab::OAuth::Provider.stub(:providers).and_return(@providers_orig)
- Gitlab.config.omniauth.stub(:enabled).and_return(@omniauth_conf_orig)
- end
-
- step 'I visit sign in page' do
- visit new_user_session_path
- end
-end
diff --git a/features/steps/profile/profile.rb b/features/steps/profile/profile.rb
index 0305f7e6da0..0c60328583a 100644
--- a/features/steps/profile/profile.rb
+++ b/features/steps/profile/profile.rb
@@ -13,7 +13,7 @@ class Spinach::Features::Profile < Spinach::FeatureSteps
fill_in 'user_website_url', with: 'testurl'
fill_in 'user_location', with: 'Ukraine'
fill_in 'user_bio', with: 'I <3 GitLab'
- click_button 'Save changes'
+ click_button 'Update profile settings'
@user.reload
end
@@ -27,9 +27,7 @@ class Spinach::Features::Profile < Spinach::FeatureSteps
end
step 'I change my avatar' do
- attach_file(:user_avatar, File.join(Rails.root, 'spec', 'fixtures', 'banana_sample.gif'))
- click_button "Save changes"
- @user.reload
+ attach_avatar
end
step 'I should see new avatar' do
@@ -42,9 +40,7 @@ class Spinach::Features::Profile < Spinach::FeatureSteps
end
step 'I have an avatar' do
- attach_file(:user_avatar, File.join(Rails.root, 'spec', 'fixtures', 'banana_sample.gif'))
- click_button "Save changes"
- @user.reload
+ attach_avatar
end
step 'I remove my avatar' do
@@ -68,7 +64,7 @@ class Spinach::Features::Profile < Spinach::FeatureSteps
page.within '.update-password' do
fill_in "user_password", with: "22233344"
fill_in "user_password_confirmation", with: "22233344"
- click_button "Save"
+ click_button "Save password"
end
end
@@ -77,7 +73,7 @@ class Spinach::Features::Profile < Spinach::FeatureSteps
fill_in "user_current_password", with: "12345678"
fill_in "user_password", with: "22233344"
fill_in "user_password_confirmation", with: "22233344"
- click_button "Save"
+ click_button "Save password"
end
end
@@ -86,7 +82,7 @@ class Spinach::Features::Profile < Spinach::FeatureSteps
fill_in "user_current_password", with: "12345678"
fill_in "user_password", with: "password"
fill_in "user_password_confirmation", with: "confirmation"
- click_button "Save"
+ click_button "Save password"
end
end
@@ -97,7 +93,7 @@ class Spinach::Features::Profile < Spinach::FeatureSteps
end
step "I should see a password error message" do
- page.within '.alert' do
+ page.within '.alert-danger' do
expect(page).to have_content "Password confirmation doesn't match"
end
end
@@ -233,4 +229,16 @@ class Spinach::Features::Profile < Spinach::FeatureSteps
step "I see that application is removed" do
expect(page.find(".oauth-applications")).not_to have_content "test_changed"
end
+
+ def attach_avatar
+ attach_file :user_avatar, Rails.root.join(*%w(spec fixtures banana_sample.gif))
+
+ page.find('#user_avatar_crop_x', visible: false).set('0')
+ page.find('#user_avatar_crop_y', visible: false).set('0')
+ page.find('#user_avatar_crop_size', visible: false).set('256')
+
+ click_button "Update profile settings"
+
+ @user.reload
+ end
end
diff --git a/features/steps/profile/ssh_keys.rb b/features/steps/profile/ssh_keys.rb
index c7f879d247d..a400488a532 100644
--- a/features/steps/profile/ssh_keys.rb
+++ b/features/steps/profile/ssh_keys.rb
@@ -7,8 +7,8 @@ class Spinach::Features::ProfileSshKeys < Spinach::FeatureSteps
end
end
- step 'I click link "Add new"' do
- click_link "Add SSH Key"
+ step 'I should see new ssh key form' do
+ expect(page).to have_content("Add an SSH key")
end
step 'I submit new ssh key "Laptop"' do
diff --git a/features/steps/project/badges/build.rb b/features/steps/project/badges/build.rb
index cbfc35bed65..47540f356e9 100644
--- a/features/steps/project/badges/build.rb
+++ b/features/steps/project/badges/build.rb
@@ -20,6 +20,10 @@ class Spinach::Features::ProjectBadgesBuild < Spinach::FeatureSteps
expect_badge('running')
end
+ step 'I should see a badge that has not been cached' do
+ expect(page.response_headers).to include('Cache-Control' => 'no-cache')
+ end
+
def expect_badge(status)
svg = Nokogiri::XML.parse(page.body)
expect(page.response_headers).to include('Content-Type' => 'image/svg+xml')
diff --git a/features/steps/project/builds/summary.rb b/features/steps/project/builds/summary.rb
index 4f94fc96354..e9e2359146e 100644
--- a/features/steps/project/builds/summary.rb
+++ b/features/steps/project/builds/summary.rb
@@ -4,10 +4,36 @@ class Spinach::Features::ProjectBuildsSummary < Spinach::FeatureSteps
include SharedBuilds
include RepoHelpers
+ step 'I see coverage' do
+ page.within('td.coverage') do
+ expect(page).to have_content "99.9%"
+ end
+ end
+
step 'I see button to CI Lint' do
page.within('.nav-controls') do
ci_lint_tool_link = page.find_link('CI Lint')
expect(ci_lint_tool_link[:href]).to eq ci_lint_path
end
end
+
+ step 'I click erase build button' do
+ click_link 'Erase'
+ end
+
+ step 'recent build has been erased' do
+ expect(@build.artifacts_file.exists?).to be_falsy
+ expect(@build.artifacts_metadata.exists?).to be_falsy
+ expect(@build.trace).to be_empty
+ end
+
+ step 'recent build summary does not have artifacts widget' do
+ expect(page).to have_no_css('.artifacts')
+ end
+
+ step 'recent build summary contains information saying that build has been erased' do
+ page.within('.erased') do
+ expect(page).to have_content 'Build has been erased'
+ end
+ end
end
diff --git a/features/steps/project/commits/commits.rb b/features/steps/project/commits/commits.rb
index f9fd7332464..93c37bf507f 100644
--- a/features/steps/project/commits/commits.rb
+++ b/features/steps/project/commits/commits.rb
@@ -126,8 +126,11 @@ class Spinach::Features::ProjectCommits < Spinach::FeatureSteps
end
step 'I visit big commit page' do
- stub_const('Commit::DIFF_SAFE_FILES', 20)
- visit namespace_project_commit_path(@project.namespace, @project, sample_big_commit.id)
+ # Create a temporary scope to ensure that the stub_const is removed after user
+ RSpec::Mocks.with_temporary_scope do
+ stub_const('Gitlab::Git::DiffCollection::DEFAULT_LIMITS', { max_lines: 1, max_files: 1 })
+ visit namespace_project_commit_path(@project.namespace, @project, sample_big_commit.id)
+ end
end
step 'I see big commit warning' do
diff --git a/features/steps/project/commits/revert.rb b/features/steps/project/commits/revert.rb
new file mode 100644
index 00000000000..94a5d4e2e4d
--- /dev/null
+++ b/features/steps/project/commits/revert.rb
@@ -0,0 +1,40 @@
+class Spinach::Features::RevertCommits < Spinach::FeatureSteps
+ include SharedAuthentication
+ include SharedProject
+ include SharedPaths
+ include SharedDiffNote
+ include RepoHelpers
+
+ step 'I click on commit link' do
+ visit namespace_project_commit_path(@project.namespace, @project, sample_commit.id)
+ end
+
+ step 'I click on the revert button' do
+ find("a[href='#modal-revert-commit']").click
+ end
+
+ step 'I revert the changes directly' do
+ page.within('#modal-revert-commit') do
+ uncheck 'create_merge_request'
+ click_button 'Revert'
+ end
+ end
+
+ step 'I should see the revert commit notice' do
+ page.should have_content('The commit has been successfully reverted.')
+ end
+
+ step 'I should see a revert error' do
+ page.should have_content('Sorry, we cannot revert this commit automatically.')
+ end
+
+ step 'I revert the changes in a new merge request' do
+ page.within('#modal-revert-commit') do
+ click_button 'Revert'
+ end
+ end
+
+ step 'I should see the new merge request notice' do
+ page.should have_content('The commit has been successfully reverted. You can now submit a merge request to get this change into the original branch.')
+ end
+end
diff --git a/features/steps/project/fork.rb b/features/steps/project/fork.rb
index 5810276ced3..527f7853da9 100644
--- a/features/steps/project/fork.rb
+++ b/features/steps/project/fork.rb
@@ -62,6 +62,12 @@ class Spinach::Features::ProjectFork < Spinach::FeatureSteps
end
end
+ step 'I make forked repo invalid' do
+ project = @user.fork_of(@project)
+ project.path = 'test-crappy-path'
+ project.save!
+ end
+
step 'There is an existent fork of the "Shop" project' do
user = create(:user, name: 'Mike')
@forked_project = Projects::ForkService.new(@project, user).execute
diff --git a/features/steps/project/issues/award_emoji.rb b/features/steps/project/issues/award_emoji.rb
index 69695d493f3..277c63914d1 100644
--- a/features/steps/project/issues/award_emoji.rb
+++ b/features/steps/project/issues/award_emoji.rb
@@ -8,6 +8,15 @@ class Spinach::Features::AwardEmoji < Spinach::FeatureSteps
visit namespace_project_issue_path(@project.namespace, @project, @issue)
end
+ step 'I click the thumbsup award Emoji' do
+ page.within '.awards' do
+ thumbsup = page.find('.award .emoji-1F44D')
+ thumbsup.click
+ thumbsup.hover
+ sleep 0.3
+ end
+ end
+
step 'I click to emoji-picker' do
page.within '.awards-controls' do
page.find('.add-award').click
@@ -37,9 +46,28 @@ class Spinach::Features::AwardEmoji < Spinach::FeatureSteps
end
step 'I have award added' do
+ sleep 0.2
+
page.within '.awards' do
expect(page).to have_selector '.award'
expect(page.find('.award.active .counter')).to have_content '1'
+ expect(page.find('.award.active')['data-original-title']).to eq('me')
+ end
+ end
+
+ step 'I have no awards added' do
+ page.within '.awards' do
+ expect(page).to have_selector '.award'
+ expect(page.all('.award').size).to eq(2)
+
+ # Check tooltip data
+ page.all('.award').each do |element|
+ expect(element['title']).to eq("")
+ end
+
+ page.all('.award .counter').each do |element|
+ expect(element).to have_content '0'
+ end
end
end
@@ -68,6 +96,7 @@ class Spinach::Features::AwardEmoji < Spinach::FeatureSteps
end
step 'The search field is focused' do
- page.evaluate_script("document.activeElement.id").should eq "emoji_search"
+ expect(page).to have_selector('#emoji_search')
+ expect(page.evaluate_script('document.activeElement.id')).to eq('emoji_search')
end
end
diff --git a/features/steps/project/issues/issues.rb b/features/steps/project/issues/issues.rb
index d556b73f9fd..d9842ccf95e 100644
--- a/features/steps/project/issues/issues.rb
+++ b/features/steps/project/issues/issues.rb
@@ -54,6 +54,10 @@ class Spinach::Features::ProjectIssues < Spinach::FeatureSteps
expect(page).to have_content "Release 0.4"
end
+ step 'I should see issue "Tweet control"' do
+ expect(page).to have_content "Tweet control"
+ end
+
step 'I click link "New Issue"' do
click_link "New Issue"
end
@@ -170,6 +174,13 @@ class Spinach::Features::ProjectIssues < Spinach::FeatureSteps
author: project.users.first)
end
+ step 'project "Shop" have "Bugfix" open issue' do
+ create(:issue,
+ title: "Bugfix",
+ project: project,
+ author: project.users.first)
+ end
+
step 'project "Shop" have "Release 0.3" closed issue' do
create(:closed_issue,
title: "Release 0.3",
@@ -177,6 +188,56 @@ class Spinach::Features::ProjectIssues < Spinach::FeatureSteps
author: project.users.first)
end
+ step 'issue "Release 0.4" have 2 upvotes and 1 downvote' do
+ issue = Issue.find_by(title: 'Release 0.4')
+ create_list(:upvote_note, 2, project: project, noteable: issue)
+ create(:downvote_note, project: project, noteable: issue)
+ end
+
+ step 'issue "Tweet control" have 1 upvote and 2 downvotes' do
+ issue = Issue.find_by(title: 'Tweet control')
+ create(:upvote_note, project: project, noteable: issue)
+ create_list(:downvote_note, 2, project: project, noteable: issue)
+ end
+
+ step 'The list should be sorted by "Least popular"' do
+ page.within '.issues-list' do
+ page.within 'li.issue:nth-child(1)' do
+ expect(page).to have_content 'Tweet control'
+ expect(page).to have_content '1 2'
+ end
+
+ page.within 'li.issue:nth-child(2)' do
+ expect(page).to have_content 'Release 0.4'
+ expect(page).to have_content '2 1'
+ end
+
+ page.within 'li.issue:nth-child(3)' do
+ expect(page).to have_content 'Bugfix'
+ expect(page).to_not have_content '0 0'
+ end
+ end
+ end
+
+ step 'The list should be sorted by "Most popular"' do
+ page.within '.issues-list' do
+ page.within 'li.issue:nth-child(1)' do
+ expect(page).to have_content 'Release 0.4'
+ expect(page).to have_content '2 1'
+ end
+
+ page.within 'li.issue:nth-child(2)' do
+ expect(page).to have_content 'Tweet control'
+ expect(page).to have_content '1 2'
+ end
+
+ page.within 'li.issue:nth-child(3)' do
+ expect(page).to have_content 'Bugfix'
+ expect(page).to_not have_content '0 0'
+ end
+ end
+ end
+
step 'empty project "Empty Project"' do
create :empty_project, name: 'Empty Project', namespace: @user.namespace
end
@@ -294,11 +355,8 @@ class Spinach::Features::ProjectIssues < Spinach::FeatureSteps
end
end
- step 'I should see "Release 0.4" at the top' do
- expect(page.find('ul.content-list.issues-list li.issue:first-child')).to have_content("Release 0.4")
- end
-
def filter_issue(text)
fill_in 'issue_search', with: text
end
+
end
diff --git a/features/steps/project/issues/milestones.rb b/features/steps/project/issues/milestones.rb
index e2eda511497..4faa0f4707c 100644
--- a/features/steps/project/issues/milestones.rb
+++ b/features/steps/project/issues/milestones.rb
@@ -59,7 +59,7 @@ class Spinach::Features::ProjectIssuesMilestones < Spinach::FeatureSteps
end
step 'I should see 3 issues' do
- expect(page).to have_selector('#tab-issues li.issue-row', count: 4)
+ expect(page).to have_selector('#tab-issues li.issuable-row', count: 4)
end
step 'I click link to remove milestone' do
diff --git a/features/steps/project/merge_requests.rb b/features/steps/project/merge_requests.rb
index 337893e6209..c19b15bc9ed 100644
--- a/features/steps/project/merge_requests.rb
+++ b/features/steps/project/merge_requests.rb
@@ -60,7 +60,6 @@ class Spinach::Features::ProjectMergeRequests < Spinach::FeatureSteps
expect(page).not_to have_content "Feature NS-03"
end
-
step 'I should not see "Bug NS-04" in merge requests' do
expect(page).not_to have_content "Bug NS-04"
end
@@ -121,6 +120,22 @@ class Spinach::Features::ProjectMergeRequests < Spinach::FeatureSteps
author: project.users.first)
end
+ step 'project "Shop" have "Bug NS-07" open merge request with rebased branch' do
+ create(:merge_request, :rebased,
+ title: "Bug NS-07",
+ source_project: project,
+ target_project: project,
+ author: project.users.first)
+ end
+
+ step 'project "Shop" have "Bug NS-08" open merge request with diverged branch' do
+ create(:merge_request, :diverged,
+ title: "Bug NS-08",
+ source_project: project,
+ target_project: project,
+ author: project.users.first)
+ end
+
step 'project "Shop" have "Feature NS-03" closed merge request' do
create(:closed_merge_request,
title: "Feature NS-03",
@@ -138,6 +153,56 @@ class Spinach::Features::ProjectMergeRequests < Spinach::FeatureSteps
author: project.users.first)
end
+ step 'merge request "Bug NS-04" have 2 upvotes and 1 downvote' do
+ merge_request = MergeRequest.find_by(title: 'Bug NS-04')
+ create_list(:upvote_note, 2, project: project, noteable: merge_request)
+ create(:downvote_note, project: project, noteable: merge_request)
+ end
+
+ step 'merge request "Bug NS-06" have 1 upvote and 2 downvotes' do
+ merge_request = MergeRequest.find_by(title: 'Bug NS-06')
+ create(:upvote_note, project: project, noteable: merge_request)
+ create_list(:downvote_note, 2, project: project, noteable: merge_request)
+ end
+
+ step 'The list should be sorted by "Least popular"' do
+ page.within '.mr-list' do
+ page.within 'li.merge-request:nth-child(1)' do
+ expect(page).to have_content 'Bug NS-06'
+ expect(page).to have_content '1 2'
+ end
+
+ page.within 'li.merge-request:nth-child(2)' do
+ expect(page).to have_content 'Bug NS-04'
+ expect(page).to have_content '2 1'
+ end
+
+ page.within 'li.merge-request:nth-child(3)' do
+ expect(page).to have_content 'Bug NS-05'
+ expect(page).to_not have_content '0 0'
+ end
+ end
+ end
+
+ step 'The list should be sorted by "Most popular"' do
+ page.within '.mr-list' do
+ page.within 'li.merge-request:nth-child(1)' do
+ expect(page).to have_content 'Bug NS-04'
+ expect(page).to have_content '2 1'
+ end
+
+ page.within 'li.merge-request:nth-child(2)' do
+ expect(page).to have_content 'Bug NS-06'
+ expect(page).to have_content '1 2'
+ end
+
+ page.within 'li.merge-request:nth-child(3)' do
+ expect(page).to have_content 'Bug NS-05'
+ expect(page).to_not have_content '0 0'
+ end
+ end
+ end
+
step 'I click on the Changes tab' do
page.within '.merge-request-tabs' do
click_link 'Changes'
@@ -440,12 +505,16 @@ class Spinach::Features::ProjectMergeRequests < Spinach::FeatureSteps
end
end
- step 'I should see "Bug NS-05" at the top' do
- expect(page.find('ul.content-list.mr-list li.merge-request:first-child')).to have_content("Bug NS-05")
+ step 'I should see the diverged commits count' do
+ page.within ".mr-source-target" do
+ expect(page).to have_content /([0-9]+ commits behind)/
+ end
end
- step 'I should see "Bug NS-04" at the top' do
- expect(page.find('ul.content-list.mr-list li.merge-request:first-child')).to have_content("Bug NS-04")
+ step 'I should not see the diverged commits count' do
+ page.within ".mr-source-target" do
+ expect(page).not_to have_content /([0-9]+ commit[s]? behind)/
+ end
end
def merge_request
diff --git a/features/steps/project/merge_requests/acceptance.rb b/features/steps/project/merge_requests/acceptance.rb
index 2685f5fd6b4..4fda0731e2f 100644
--- a/features/steps/project/merge_requests/acceptance.rb
+++ b/features/steps/project/merge_requests/acceptance.rb
@@ -29,7 +29,7 @@ class Spinach::Features::ProjectMergeRequestsAcceptance < Spinach::FeatureSteps
step 'There is an open Merge Request' do
@user = create(:user)
@project = create(:project, :public)
- @project_member = create(:project_member, user: @user, project: @project, access_level: ProjectMember::DEVELOPER)
+ @project_member = create(:project_member, :developer, user: @user, project: @project)
@merge_request = create(:merge_request, :with_diffs, :simple, source_project: @project)
end
diff --git a/features/steps/project/merge_requests/revert.rb b/features/steps/project/merge_requests/revert.rb
new file mode 100644
index 00000000000..efbc4831ce1
--- /dev/null
+++ b/features/steps/project/merge_requests/revert.rb
@@ -0,0 +1,56 @@
+class Spinach::Features::RevertMergeRequests < Spinach::FeatureSteps
+ include LoginHelpers
+ include GitlabRoutingHelper
+
+ step 'I click on the revert button' do
+ find("a[href='#modal-revert-commit']").click
+ end
+
+ step 'I revert the changes directly' do
+ page.within('#modal-revert-commit') do
+ uncheck 'create_merge_request'
+ click_button 'Revert'
+ end
+ end
+
+ step 'I should see the revert merge request notice' do
+ page.should have_content('The merge request has been successfully reverted.')
+ end
+
+ step 'I should not see the revert button' do
+ expect(page).not_to have_selector(:xpath, "a[href='#modal-revert-commit']")
+ end
+
+ step 'I am on the Merge Request detail page' do
+ visit merge_request_path(@merge_request)
+ end
+
+ step 'I click on Accept Merge Request' do
+ click_button('Accept Merge Request')
+ end
+
+ step 'I am signed in as a developer of the project' do
+ login_as(@user)
+ end
+
+ step 'There is an open Merge Request' do
+ @user = create(:user)
+ @project = create(:project, :public)
+ @project_member = create(:project_member, :developer, user: @user, project: @project)
+ @merge_request = create(:merge_request, :with_diffs, :simple, source_project: @project)
+ end
+
+ step 'I should see a revert error' do
+ page.should have_content('Sorry, we cannot revert this merge request automatically.')
+ end
+
+ step 'I revert the changes in a new merge request' do
+ page.within('#modal-revert-commit') do
+ click_button 'Revert'
+ end
+ end
+
+ step 'I should see the new merge request notice' do
+ page.should have_content('The merge request has been successfully reverted. You can now submit a merge request to get this change into the original branch.')
+ end
+end
diff --git a/features/steps/project/project.rb b/features/steps/project/project.rb
index 37bf52b4a95..ef185861e00 100644
--- a/features/steps/project/project.rb
+++ b/features/steps/project/project.rb
@@ -144,4 +144,14 @@ class Spinach::Features::Project < Spinach::FeatureSteps
expect(page).to have_content 'Notification settings saved'
end
end
+
+ step 'I create bare repo' do
+ click_link 'Create empty bare repository'
+ end
+
+ step 'I should see command line instructions' do
+ page.within ".empty_wrapper" do
+ expect(page).to have_content("Command line instructions")
+ end
+ end
end
diff --git a/features/steps/project/project_milestone.rb b/features/steps/project/project_milestone.rb
new file mode 100644
index 00000000000..2508c09e36d
--- /dev/null
+++ b/features/steps/project/project_milestone.rb
@@ -0,0 +1,59 @@
+class Spinach::Features::ProjectMilestone < Spinach::FeatureSteps
+ include SharedAuthentication
+ include SharedProject
+ include SharedPaths
+
+ step 'milestone has issue "Bugfix1" with labels: "bug", "feature"' do
+ project = Project.find_by(name: "Shop")
+ milestone = project.milestones.find_by(title: 'v2.2')
+ issue = create(:issue, title: "Bugfix1", project: project, milestone: milestone)
+ issue.labels << project.labels.find_by(title: 'bug')
+ issue.labels << project.labels.find_by(title: 'feature')
+ end
+
+ step 'milestone has issue "Bugfix2" with labels: "bug", "enhancement"' do
+ project = Project.find_by(name: "Shop")
+ milestone = project.milestones.find_by(title: 'v2.2')
+ issue = create(:issue, title: "Bugfix2", project: project, milestone: milestone)
+ issue.labels << project.labels.find_by(title: 'bug')
+ issue.labels << project.labels.find_by(title: 'enhancement')
+ end
+
+ step 'project "Shop" has milestone "v2.2"' do
+ project = Project.find_by(name: "Shop")
+ milestone = create(:milestone,
+ title: "v2.2",
+ project: project,
+ description: "# Description header"
+ )
+ 3.times { create(:issue, project: project, milestone: milestone) }
+ end
+
+ step 'I should see the list of labels' do
+ expect(page).to have_selector('ul.manage-labels-list')
+ end
+
+ step 'I should see the labels "bug", "enhancement" and "feature"' do
+ page.within('#tab-issues') do
+ expect(page).to have_content 'bug'
+ expect(page).to have_content 'enhancement'
+ expect(page).to have_content 'feature'
+ end
+ end
+
+ step 'I should see the "bug" label listed only once' do
+ page.within('#tab-labels') do
+ expect(page).to have_content('bug', count: 1)
+ end
+ end
+
+ step 'I click link "v2.2"' do
+ click_link "v2.2"
+ end
+
+ step 'I click link "Labels"' do
+ page.within('.nav-links') do
+ page.find(:xpath, "//a[@href='#tab-labels']").click
+ end
+ end
+end
diff --git a/features/steps/project/source/markdown_render.rb b/features/steps/project/source/markdown_render.rb
index 3a4f7a6e01c..2134dae168a 100644
--- a/features/steps/project/source/markdown_render.rb
+++ b/features/steps/project/source/markdown_render.rb
@@ -238,7 +238,11 @@ class Spinach::Features::ProjectSourceMarkdownRender < Spinach::FeatureSteps
step 'I see new wiki page named test' do
expect(current_path).to eq namespace_project_wiki_path(@project.namespace, @project, "test")
- expect(page).to have_content "Edit Page test"
+
+ page.within(:css, ".nav-text") do
+ expect(page).to have_content "Test"
+ expect(page).to have_content "Edit Page"
+ end
end
When 'I go back to wiki page home' do
@@ -252,7 +256,11 @@ class Spinach::Features::ProjectSourceMarkdownRender < Spinach::FeatureSteps
step 'I see Gitlab API document' do
expect(current_path).to eq namespace_project_wiki_path(@project.namespace, @project, "api")
- expect(page).to have_content "Edit Page api"
+
+ page.within(:css, ".nav-text") do
+ expect(page).to have_content "Edit"
+ expect(page).to have_content "Api"
+ end
end
step 'I click on Rake tasks link' do
@@ -261,7 +269,11 @@ class Spinach::Features::ProjectSourceMarkdownRender < Spinach::FeatureSteps
step 'I see Rake tasks directory' do
expect(current_path).to eq namespace_project_wiki_path(@project.namespace, @project, "raketasks")
- expect(page).to have_content "Edit Page raketasks"
+
+ page.within(:css, ".nav-text") do
+ expect(page).to have_content "Edit"
+ expect(page).to have_content "Rake"
+ end
end
step 'I go directory which contains README file' do
diff --git a/features/steps/project/wiki.rb b/features/steps/project/wiki.rb
index 2a735afbe7b..223b7277b51 100644
--- a/features/steps/project/wiki.rb
+++ b/features/steps/project/wiki.rb
@@ -120,7 +120,7 @@ class Spinach::Features::ProjectWiki < Spinach::FeatureSteps
step 'I should see the new wiki page form' do
expect(current_path).to match('wikis/image.jpg')
expect(page).to have_content('New Wiki Page')
- expect(page).to have_content('Edit Page image.jpg')
+ expect(page).to have_content('Edit Page')
end
step 'I create a New page with paths' do
@@ -159,7 +159,9 @@ class Spinach::Features::ProjectWiki < Spinach::FeatureSteps
end
step 'I should see the page history' do
- expect(page).to have_content('History for')
+ page.within(:css, ".nav-text") do
+ expect(page).to have_content('History')
+ end
end
step 'I search for Wiki content' do
diff --git a/features/steps/search.rb b/features/steps/search.rb
index 79273cbad9a..48ea3fa3876 100644
--- a/features/steps/search.rb
+++ b/features/steps/search.rb
@@ -18,6 +18,11 @@ class Spinach::Features::Search < Spinach::FeatureSteps
click_button "Search"
end
+ step 'I search for "rspec" on project page' do
+ fill_in "search", with: "rspec"
+ click_button "Go"
+ end
+
step 'I search for "Wiki content"' do
fill_in "dashboard_search", with: "content"
click_button "Search"
@@ -103,4 +108,8 @@ class Spinach::Features::Search < Spinach::FeatureSteps
@wiki = ::ProjectWiki.new(project, current_user)
@wiki.create_page("test_wiki", "Some Wiki content", :markdown, "first commit")
end
+
+ step 'project "Shop" is public' do
+ project.update_attributes(visibility_level: Project::PUBLIC)
+ end
end
diff --git a/features/steps/shared/builds.rb b/features/steps/shared/builds.rb
index fa54c93df0f..f33ed7834fe 100644
--- a/features/steps/shared/builds.rb
+++ b/features/steps/shared/builds.rb
@@ -5,9 +5,13 @@ module SharedBuilds
@project.enable_ci
end
+ step 'project has coverage enabled' do
+ @project.update_attribute(:build_coverage_regex, /Coverage (\d+)%/)
+ end
+
step 'project has a recent build' do
@ci_commit = create(:ci_commit, project: @project, sha: @project.commit.sha)
- @build = create(:ci_build, commit: @ci_commit)
+ @build = create(:ci_build_with_coverage, commit: @ci_commit)
end
step 'recent build is successful' do
@@ -42,6 +46,10 @@ module SharedBuilds
@build.update_attributes(artifacts_metadata: gzip)
end
+ step 'recent build has a build trace' do
+ @build.trace = 'build trace'
+ end
+
step 'download of build artifacts archive starts' do
expect(page.response_headers['Content-Type']).to eq 'application/zip'
expect(page.response_headers['Content-Transfer-Encoding']).to eq 'binary'
diff --git a/features/steps/shared/issuable.rb b/features/steps/shared/issuable.rb
index 25c2b476f43..ae10c6069a9 100644
--- a/features/steps/shared/issuable.rb
+++ b/features/steps/shared/issuable.rb
@@ -113,12 +113,46 @@ module SharedIssuable
end
end
+ step 'I sort the list by "Least popular"' do
+ find('button.dropdown-toggle.btn').click
+
+ page.within('ul.dropdown-menu.dropdown-menu-align-right li') do
+ click_link 'Least popular'
+ end
+ end
+
+ step 'I sort the list by "Most popular"' do
+ find('button.dropdown-toggle.btn').click
+
+ page.within('ul.dropdown-menu.dropdown-menu-align-right li') do
+ click_link 'Most popular'
+ end
+ end
+
step 'The list should be sorted by "Oldest updated"' do
page.within('div.dropdown.inline.prepend-left-10') do
expect(page.find('button.dropdown-toggle.btn')).to have_content('Oldest updated')
end
end
+ step 'I should see "1 of 1" in the sidebar' do
+ expect_sidebar_content('1 of 1')
+ end
+
+ step 'I should see "1 of 2" in the sidebar' do
+ expect_sidebar_content('1 of 2')
+ end
+
+ step 'I should see "2 of 2" in the sidebar' do
+ expect_sidebar_content('2 of 2')
+ end
+
+ step 'I click link "Next" in the sidebar' do
+ page.within '.issuable-sidebar' do
+ click_link 'Next'
+ end
+ end
+
def create_issuable_for_project(project_name:, title:, type: :issue)
project = Project.find_by(name: project_name)
@@ -159,4 +193,10 @@ module SharedIssuable
expect(page).to have_content("mentioned in #{issuable.class.to_s.titleize.downcase} #{issuable.to_reference(project)}")
end
+ def expect_sidebar_content(content)
+ page.within '.issuable-sidebar' do
+ expect(page).to have_content content
+ end
+ end
+
end
diff --git a/features/steps/shared/note.rb b/features/steps/shared/note.rb
index eb6df61b8e6..444d6726f99 100644
--- a/features/steps/shared/note.rb
+++ b/features/steps/shared/note.rb
@@ -144,11 +144,4 @@ module SharedNote
expect(page).to have_content("+1 Awesome!")
end
end
-
- step 'I sort the list by "Last updated"' do
- find('button.dropdown-toggle.btn').click
- page.within('ul.dropdown-menu.dropdown-menu-align-right li') do
- click_link "Last updated"
- end
- end
end
diff --git a/features/steps/shared/paths.rb b/features/steps/shared/paths.rb
index 2c854ac7bf9..da9d1503ebc 100644
--- a/features/steps/shared/paths.rb
+++ b/features/steps/shared/paths.rb
@@ -7,6 +7,10 @@ module SharedPaths
visit new_project_path
end
+ step 'I visit login page' do
+ visit new_user_session_path
+ end
+
# ----------------------------------------
# User
# ----------------------------------------
@@ -103,6 +107,10 @@ module SharedPaths
visit dashboard_groups_path
end
+ step 'I visit dashboard todos page' do
+ visit dashboard_todos_path
+ end
+
step 'I should be redirected to the dashboard groups page' do
expect(current_path).to eq dashboard_groups_path
end
@@ -183,6 +191,10 @@ module SharedPaths
visit admin_groups_path
end
+ step 'I visit admin appearance page' do
+ visit admin_appearances_path
+ end
+
step 'I visit admin teams page' do
visit admin_teams_path
end
@@ -376,13 +388,19 @@ module SharedPaths
end
step 'I visit merge request page "Bug NS-04"' do
- mr = MergeRequest.find_by(title: "Bug NS-04")
- visit namespace_project_merge_request_path(mr.target_project.namespace, mr.target_project, mr)
+ visit merge_request_path("Bug NS-04")
end
step 'I visit merge request page "Bug NS-05"' do
- mr = MergeRequest.find_by(title: "Bug NS-05")
- visit namespace_project_merge_request_path(mr.target_project.namespace, mr.target_project, mr)
+ visit merge_request_path("Bug NS-05")
+ end
+
+ step 'I visit merge request page "Bug NS-07"' do
+ visit merge_request_path("Bug NS-07")
+ end
+
+ step 'I visit merge request page "Bug NS-08"' do
+ visit merge_request_path("Bug NS-08")
end
step 'I visit merge request page "Bug CO-01"' do
@@ -443,6 +461,10 @@ module SharedPaths
visit namespace_project_path(project.namespace, project)
end
+ step "I should not see command line instructions" do
+ expect(page).not_to have_css('.empty_wrapper')
+ end
+
# ----------------------------------------
# Public Projects
# ----------------------------------------
@@ -487,6 +509,11 @@ module SharedPaths
Project.find_by!(name: 'Shop')
end
+ def merge_request_path(title)
+ mr = MergeRequest.find_by(title: title)
+ namespace_project_merge_request_path(mr.target_project.namespace, mr.target_project, mr)
+ end
+
# ----------------------------------------
# Errors
# ----------------------------------------
diff --git a/features/steps/shared/user.rb b/features/steps/shared/user.rb
index f0721094ee3..9856c510aa0 100644
--- a/features/steps/shared/user.rb
+++ b/features/steps/shared/user.rb
@@ -26,4 +26,20 @@ module SharedUser
step 'I have no ssh keys' do
@user.keys.delete_all
end
+
+ step 'I click on "Personal projects" tab' do
+ page.within '.nav-links' do
+ click_link 'Personal projects'
+ end
+
+ expect(page).to have_css('.tab-content #projects.active')
+ end
+
+ step 'I click on "Contributed projects" tab' do
+ page.within '.nav-links' do
+ click_link 'Contributed projects'
+ end
+
+ expect(page).to have_css('.tab-content #contributed.active')
+ end
end
diff --git a/features/support/capybara.rb b/features/support/capybara.rb
index 38069ff8835..f33379f76c9 100644
--- a/features/support/capybara.rb
+++ b/features/support/capybara.rb
@@ -6,7 +6,7 @@ timeout = (ENV['CI'] || ENV['CI_SERVER']) ? 90 : 15
Capybara.javascript_driver = :poltergeist
Capybara.register_driver :poltergeist do |app|
- Capybara::Poltergeist::Driver.new(app, js_errors: true, timeout: timeout)
+ Capybara::Poltergeist::Driver.new(app, js_errors: true, timeout: timeout, window_size: [1366, 768])
end
Capybara.default_wait_time = timeout
diff --git a/features/user.feature b/features/user.feature
index 35eae842e77..e0cadba30a1 100644
--- a/features/user.feature
+++ b/features/user.feature
@@ -5,10 +5,12 @@ Feature: User
# Signed out
+ @javascript
Scenario: I visit user "John Doe" page while not signed in when he owns a public project
Given "John Doe" owns internal project "Internal"
And "John Doe" owns public project "Community"
When I visit user "John Doe" page
+ And I click on "Personal projects" tab
Then I should see user "John Doe" page
And I should not see project "Enterprise"
And I should not see project "Internal"
@@ -16,28 +18,34 @@ Feature: User
# Signed in as someone else
+ @javascript
Scenario: I visit user "John Doe" page while signed in as someone else when he owns a public project
Given "John Doe" owns public project "Community"
And "John Doe" owns internal project "Internal"
And I sign in as a user
When I visit user "John Doe" page
+ And I click on "Personal projects" tab
Then I should see user "John Doe" page
And I should not see project "Enterprise"
And I should see project "Internal"
And I should see project "Community"
+ @javascript
Scenario: I visit user "John Doe" page while signed in as someone else when he is not authorized to a public project
Given "John Doe" owns internal project "Internal"
And I sign in as a user
When I visit user "John Doe" page
+ And I click on "Personal projects" tab
Then I should see user "John Doe" page
And I should not see project "Enterprise"
And I should see project "Internal"
And I should not see project "Community"
+ @javascript
Scenario: I visit user "John Doe" page while signed in as someone else when he is not authorized to a project I can see
Given I sign in as a user
When I visit user "John Doe" page
+ And I click on "Personal projects" tab
Then I should see user "John Doe" page
And I should not see project "Enterprise"
And I should not see project "Internal"
@@ -45,19 +53,23 @@ Feature: User
# Signed in as the user himself
+ @javascript
Scenario: I visit user "John Doe" page while signed in as "John Doe" when he has a public project
Given "John Doe" owns internal project "Internal"
And "John Doe" owns public project "Community"
And I sign in as "John Doe"
When I visit user "John Doe" page
+ And I click on "Personal projects" tab
Then I should see user "John Doe" page
And I should see project "Enterprise"
And I should see project "Internal"
And I should see project "Community"
+ @javascript
Scenario: I visit user "John Doe" page while signed in as "John Doe" when he has no public project
Given I sign in as "John Doe"
When I visit user "John Doe" page
+ And I click on "Personal projects" tab
Then I should see user "John Doe" page
And I should see project "Enterprise"
And I should not see project "Internal"
@@ -68,6 +80,7 @@ Feature: User
Given I sign in as a user
And "John Doe" has contributions
When I visit user "John Doe" page
+ And I click on "Contributed projects" tab
Then I should see user "John Doe" page
And I should see contributed projects
And I should see contributions calendar
diff --git a/fixtures/emojis/aliases.json b/fixtures/emojis/aliases.json
index 547ce7978b3..d3831d8045b 100644
--- a/fixtures/emojis/aliases.json
+++ b/fixtures/emojis/aliases.json
@@ -1,22 +1,35 @@
-{
+{
"northeast_pointing_airplane":"airplane_northeast",
"small_airplane":"airplane_small",
"up_pointing_small_airplane":"airplane_small_up",
"up_pointing_airplane":"airplane_up",
"left_anger_bubble":"anger_left",
"right_anger_bubble":"anger_right",
+ "keycap_asterisk":"asterisk",
+ "atom_symbol":"atom",
"ballot_box_with_ballot":"ballot_box",
"ballot_box_with_bold_check":"ballot_box_check",
"ballot_box_with_script_x":"ballot_box_x",
"ballot_script_x":"ballot_x",
+ "person_with_ball":"basketball_player",
+ "person_with_ball_tone1":"basketball_player_tone1",
+ "person_with_ball_tone2":"basketball_player_tone2",
+ "person_with_ball_tone3":"basketball_player_tone3",
+ "person_with_ball_tone4":"basketball_player_tone4",
+ "person_with_ball_tone5":"basketball_player_tone5",
"beach_with_umbrella":"beach",
+ "umbrella_on_ground":"beach_umbrella",
"bellhop_bell":"bellhop",
+ "biohazard_sign":"biohazard",
"bouquet_of_flowers":"bouquet2",
+ "archery":"bow_and_arrow",
"bullhorn_with_sound_waves":"bullhorn_waves",
"pocket calculator":"calculator",
"spiral_calendar_pad":"calendar_spiral",
"card_file_box":"card_box",
"tape_cartridge":"cartridge",
+ "bottle_with_popping_cork":"champagne",
+ "cheese_wedge":"cheese",
"city_sunrise":"city_sunset",
"mantlepiece_clock":"clock",
"clockwise_right_and_left_semicircle_arrows":"clockwise_arrows",
@@ -30,6 +43,8 @@
"couple_with_heart_mm":"couple_mm",
"couple_with_heart_ww":"couple_ww",
"lower_left_crayon":"crayon",
+ "cricket_bat_ball":"cricket",
+ "latin_cross":"cross",
"heavy_latin_cross":"cross_heavy",
"white_latin_cross":"cross_white",
"black_skull_and_crossbones":"crossbones",
@@ -60,10 +75,13 @@
"al":"flag_al",
"am":"flag_am",
"ao":"flag_ao",
+ "aq":"flag_aq",
"ar":"flag_ar",
+ "as":"flag_as",
"at":"flag_at",
"au":"flag_au",
"aw":"flag_aw",
+ "ax":"flag_ax",
"az":"flag_az",
"ba":"flag_ba",
"bb":"flag_bb",
@@ -74,37 +92,47 @@
"bh":"flag_bh",
"bi":"flag_bi",
"bj":"flag_bj",
+ "bl":"flag_bl",
"waving_black_flag":"flag_black",
"bm":"flag_bm",
"bn":"flag_bn",
"bo":"flag_bo",
+ "bq":"flag_bq",
"br":"flag_br",
"bs":"flag_bs",
"bt":"flag_bt",
+ "bv":"flag_bv",
"bw":"flag_bw",
"by":"flag_by",
"bz":"flag_bz",
"ca":"flag_ca",
+ "cc":"flag_cc",
"congo":"flag_cd",
"cf":"flag_cf",
"cg":"flag_cg",
"ch":"flag_ch",
"ci":"flag_ci",
+ "ck":"flag_ck",
"chile":"flag_cl",
"cm":"flag_cm",
"cn":"flag_cn",
"co":"flag_co",
+ "cp":"flag_cp",
"cr":"flag_cr",
"cu":"flag_cu",
"cv":"flag_cv",
+ "cw":"flag_cw",
+ "cx":"flag_cx",
"cy":"flag_cy",
"cz":"flag_cz",
"de":"flag_de",
+ "dg":"flag_dg",
"dj":"flag_dj",
"dk":"flag_dk",
"dm":"flag_dm",
"do":"flag_do",
"dz":"flag_dz",
+ "ea":"flag_ea",
"ec":"flag_ec",
"ee":"flag_ee",
"eg":"flag_eg",
@@ -112,6 +140,7 @@
"er":"flag_er",
"es":"flag_es",
"et":"flag_et",
+ "eu":"flag_eu",
"fi":"flag_fi",
"fj":"flag_fj",
"fk":"flag_fk",
@@ -122,26 +151,34 @@
"gb":"flag_gb",
"gd":"flag_gd",
"ge":"flag_ge",
+ "gf":"flag_gf",
+ "gg":"flag_gg",
"gh":"flag_gh",
"gi":"flag_gi",
"gl":"flag_gl",
"gm":"flag_gm",
"gn":"flag_gn",
+ "gp":"flag_gp",
"gq":"flag_gq",
"gr":"flag_gr",
+ "gs":"flag_gs",
"gt":"flag_gt",
"gu":"flag_gu",
"gw":"flag_gw",
"gy":"flag_gy",
"hk":"flag_hk",
+ "hm":"flag_hm",
"hn":"flag_hn",
"hr":"flag_hr",
"ht":"flag_ht",
"hu":"flag_hu",
+ "ic":"flag_ic",
"indonesia":"flag_id",
"ie":"flag_ie",
"il":"flag_il",
+ "im":"flag_im",
"in":"flag_in",
+ "io":"flag_io",
"iq":"flag_iq",
"ir":"flag_ir",
"is":"flag_is",
@@ -176,6 +213,7 @@
"mc":"flag_mc",
"md":"flag_md",
"me":"flag_me",
+ "mf":"flag_mf",
"mg":"flag_mg",
"mh":"flag_mh",
"mk":"flag_mk",
@@ -183,6 +221,8 @@
"mm":"flag_mm",
"mn":"flag_mn",
"mo":"flag_mo",
+ "mp":"flag_mp",
+ "mq":"flag_mq",
"mr":"flag_mr",
"ms":"flag_ms",
"mt":"flag_mt",
@@ -195,6 +235,7 @@
"na":"flag_na",
"nc":"flag_nc",
"ne":"flag_ne",
+ "nf":"flag_nf",
"nigeria":"flag_ng",
"ni":"flag_ni",
"nl":"flag_nl",
@@ -211,12 +252,15 @@
"ph":"flag_ph",
"pk":"flag_pk",
"pl":"flag_pl",
+ "pm":"flag_pm",
+ "pn":"flag_pn",
"pr":"flag_pr",
"ps":"flag_ps",
"pt":"flag_pt",
"pw":"flag_pw",
"py":"flag_py",
"qa":"flag_qa",
+ "re":"flag_re",
"ro":"flag_ro",
"rs":"flag_rs",
"ru":"flag_ru",
@@ -230,20 +274,27 @@
"sg":"flag_sg",
"sh":"flag_sh",
"si":"flag_si",
+ "sj":"flag_sj",
"sk":"flag_sk",
"sl":"flag_sl",
"sm":"flag_sm",
"sn":"flag_sn",
"so":"flag_so",
"sr":"flag_sr",
+ "ss":"flag_ss",
"st":"flag_st",
"sv":"flag_sv",
+ "sx":"flag_sx",
"sy":"flag_sy",
"sz":"flag_sz",
+ "ta":"flag_ta",
+ "tc":"flag_tc",
"td":"flag_td",
+ "tf":"flag_tf",
"tg":"flag_tg",
"th":"flag_th",
"tj":"flag_tj",
+ "tk":"flag_tk",
"tl":"flag_tl",
"turkmenistan":"flag_tm",
"tn":"flag_tn",
@@ -255,12 +306,14 @@
"tz":"flag_tz",
"ua":"flag_ua",
"ug":"flag_ug",
+ "um":"flag_um",
"us":"flag_us",
"uy":"flag_uy",
"uz":"flag_uz",
"va":"flag_va",
"vc":"flag_vc",
"ve":"flag_ve",
+ "vg":"flag_vg",
"vi":"flag_vi",
"vn":"flag_vn",
"vu":"flag_vu",
@@ -269,6 +322,7 @@
"ws":"flag_ws",
"xk":"flag_xk",
"ye":"flag_ye",
+ "yt":"flag_yt",
"za":"flag_za",
"zm":"flag_zm",
"zw":"flag_zw",
@@ -281,12 +335,24 @@
"frame_with_tiles":"frame_tiles",
"frame_with_an_x":"frame_x",
"anguished":"frowning",
+ "white_frowning_face":"frowning2",
+ "hammer_and_pick":"hammer_pick",
"raised_hand_with_fingers_splayed":"hand_splayed",
"reversed_raised_hand_with_fingers_splayed":"hand_splayed_reverse",
+ "raised_hand_with_fingers_splayed_tone1":"hand_splayed_tone1",
+ "raised_hand_with_fingers_splayed_tone2":"hand_splayed_tone2",
+ "raised_hand_with_fingers_splayed_tone3":"hand_splayed_tone3",
+ "raised_hand_with_fingers_splayed_tone4":"hand_splayed_tone4",
+ "raised_hand_with_fingers_splayed_tone5":"hand_splayed_tone5",
"reversed_victory_hand":"hand_victory",
+ "face_with_head_bandage":"head_bandage",
+ "heavy_heart_exclamation_mark_ornament":"heart_exclamation",
"heart_with_tip_on_the_left":"heart_tip",
+ "helmet_with_white_cross":"helmet_with_cross",
"house_buildings":"homes",
+ "hot_dog":"hotdog",
"derelict_house_building":"house_abandoned",
+ "hugging_face":"hugging",
"circled_information_source":"info",
"desert_island":"island",
"up_pointing_military_airplane":"jet_up",
@@ -300,16 +366,36 @@
"left_hand_telephone_receiver":"left_receiver",
"man_in_business_suit_levitating":"levitate",
"weight_lifter":"lifter",
+ "weight_lifter_tone1":"lifter_tone1",
+ "weight_lifter_tone2":"lifter_tone2",
+ "weight_lifter_tone3":"lifter_tone3",
+ "weight_lifter_tone4":"lifter_tone4",
+ "weight_lifter_tone5":"lifter_tone5",
"light_mark":"light_check_mark",
+ "lion":"lion_face",
"world_map":"map",
"sports_medal":"medal",
+ "sign_of_the_horns":"metal",
+ "sign_of_the_horns_tone1":"metal_tone1",
+ "sign_of_the_horns_tone2":"metal_tone2",
+ "sign_of_the_horns_tone3":"metal_tone3",
+ "sign_of_the_horns_tone4":"metal_tone4",
+ "sign_of_the_horns_tone5":"metal_tone5",
"studio_microphone":"microphone2",
"reversed_hand_with_middle_finger_extended":"middle_finger",
+ "reversed_hand_with_middle_finger_extended_tone1":"middle_finger_tone1",
+ "reversed_hand_with_middle_finger_extended_tone2":"middle_finger_tone2",
+ "reversed_hand_with_middle_finger_extended_tone3":"middle_finger_tone3",
+ "reversed_hand_with_middle_finger_extended_tone4":"middle_finger_tone4",
+ "reversed_hand_with_middle_finger_extended_tone5":"middle_finger_tone5",
+ "money_mouth_face":"money_mouth",
"lightning_mood_bubble":"mood_bubble_lightning",
"lightning_mood":"mood_lightning",
"racing_motorcycle":"motorcycle",
"snow_capped_mountain":"mountain_snow",
"one_button_mouse":"mouse_one",
+ "three_button_mouse":"mouse_three_button",
+ "nerd_face":"nerd",
"three_networked_computers":"network",
"rolled_up_newspaper":"newspaper2",
"note_page":"note",
@@ -319,27 +405,40 @@
"spiral_note_pad":"notepad_spiral",
"oil_drum":"oil",
"grandma":"older_woman",
+ "grandma_tone1":"older_woman_tone1",
+ "grandma_tone2":"older_woman_tone2",
+ "grandma_tone3":"older_woman_tone3",
+ "grandma_tone4":"older_woman_tone4",
+ "grandma_tone5":"older_woman_tone5",
"optical_disc_icon":"optical_disk",
"lower_left_paintbrush":"paintbrush",
"linked_paperclips":"paperclips",
"national_park":"park",
+ "double_vertical_bar":"pause_button",
+ "peace_symbol":"peace",
"lower_left_ballpoint_pen":"pen_ballpoint",
"lower_left_fountain_pen":"pen_fountain",
"memo":"pencil",
"lower_left_pencil":"pencil3",
"black_pennant":"pennant_black",
"white_pennant":"pennant_white",
+ "table_tennis":"ping_pong",
"no_piracy":"piracy",
+ "worship_symbol":"place_of_worship",
"shit":"poop",
"hankey":"poop",
"poo":"poop",
"prohibited_sign":"prohibited",
"film_projector":"projector",
"racing_car":"race_car",
+ "radioactive_sign":"radioactive",
"railroad_track":"railway_track",
"right_speaker_with_one_sound_wave":"right_speaker_one",
"right_speaker_with_three_sound_waves":"right_speaker_three",
+ "robot_face":"robot",
+ "face_with_rolling_eyes":"rolling_eyes",
"skeleton":"skull",
+ "skull_and_crossbones":"skull_crossbones",
"slightly_frowning_face":"slight_frown",
"slightly_smiling_face":"slight_smile",
"speaking_head_in_silhouette":"speaking_head",
@@ -348,20 +447,53 @@
"three_speech_bubbles":"speech_three",
"two_speech_bubbles":"speech_two",
"sleuth_or_spy":"spy",
+ "sleuth_or_spy_tone1":"spy_tone1",
+ "sleuth_or_spy_tone2":"spy_tone2",
+ "sleuth_or_spy_tone3":"spy_tone3",
+ "sleuth_or_spy_tone4":"spy_tone4",
+ "sleuth_or_spy_tone5":"spy_tone5",
"portable_stereo":"stereo",
"black_touchtone_telephone":"telephone_black",
"white_touchtone_telephone":"telephone_white",
+ "face_with_thermometer":"thermometer_face",
+ "thinking_face":"thinking",
"left_thought_bubble":"thought_left",
"right_thought_bubble":"thought_right",
"reversed_thumbs_down_sign":"thumbs_down_reverse",
"reversed_thumbs_up_sign":"thumbs_up_reverse",
"-1":"thumbsdown",
+ "-1_tone1":"thumbsdown_tone1",
+ "-1_tone2":"thumbsdown_tone2",
+ "-1_tone3":"thumbsdown_tone3",
+ "-1_tone4":"thumbsdown_tone4",
+ "-1_tone5":"thumbsdown_tone5",
"+1":"thumbsup",
+ "+1_tone1":"thumbsup_tone1",
+ "+1_tone2":"thumbsup_tone2",
+ "+1_tone3":"thumbsup_tone3",
+ "+1_tone4":"thumbsup_tone4",
+ "+1_tone5":"thumbsup_tone5",
+ "thunder_cloud_and_rain":"thunder_cloud_rain",
"admission_tickets":"tickets",
+ "timer_clock":"timer",
"hammer_and_wrench":"tools",
+ "next_track":"track_next",
+ "previous_track":"track_previous",
"diesel_locomotive":"train_diesel",
"triangle_with_rounded_corners":"triangle_round",
"turned_ok_hand_sign":"turned_ok_hand",
+ "unicorn_face":"unicorn",
+ "upside_down_face":"upside_down",
+ "funeral_urn":"urn",
"raised_hand_with_part_between_middle_and_ring_fingers":"vulcan",
- "left_writing_hand":"writing_hand"
-} \ No newline at end of file
+ "raised_hand_with_part_between_middle_and_ring_fingers_tone1":"vulcan_tone1",
+ "raised_hand_with_part_between_middle_and_ring_fingers_tone2":"vulcan_tone2",
+ "raised_hand_with_part_between_middle_and_ring_fingers_tone3":"vulcan_tone3",
+ "raised_hand_with_part_between_middle_and_ring_fingers_tone4":"vulcan_tone4",
+ "raised_hand_with_part_between_middle_and_ring_fingers_tone5":"vulcan_tone5",
+ "white_sun_behind_cloud":"white_sun_cloud",
+ "white_sun_behind_cloud_with_rain":"white_sun_rain_cloud",
+ "white_sun_with_small_cloud":"white_sun_small_cloud",
+ "left_writing_hand":"writing_hand",
+ "zipper_mouth_face":"zipper_mouth"
+}
diff --git a/fixtures/emojis/generate_aliases.rb b/fixtures/emojis/generate_aliases.rb
new file mode 100755
index 00000000000..8838fb9a3af
--- /dev/null
+++ b/fixtures/emojis/generate_aliases.rb
@@ -0,0 +1,18 @@
+#!/usr/bin/env ruby
+
+require 'json'
+
+aliases = {}
+
+index_file = File.expand_path("./index.json")
+index = JSON.parse(File.read(index_file))
+
+index.each_pair do |key, data|
+ data['aliases'].each do |a|
+ a.tr!(':', '')
+
+ aliases[a] = key
+ end
+end
+
+puts JSON.pretty_generate(aliases, indent: ' ', space: '', space_before: '')
diff --git a/fixtures/emojis/index.json b/fixtures/emojis/index.json
index 60ef2399e14..7f204c1a8e0 100644
--- a/fixtures/emojis/index.json
+++ b/fixtures/emojis/index.json
@@ -7,7 +7,21 @@
"category": "other",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["numbers", "perfect", "score", "100", "percent", "a", "plus", "perfect", "school", "quiz", "score", "test", "exam"],
+ "keywords": [
+ "numbers",
+ "perfect",
+ "score",
+ "100",
+ "percent",
+ "a",
+ "plus",
+ "perfect",
+ "school",
+ "quiz",
+ "score",
+ "test",
+ "exam"
+ ],
"moji": "💯"
},
"1234": {
@@ -18,7 +32,10 @@
"category": "other",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["blue-square", "numbers"],
+ "keywords": [
+ "blue-square",
+ "numbers"
+ ],
"moji": "🔢"
},
"8ball": {
@@ -29,7 +46,14 @@
"category": "objects",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["pool", "billiards", "eight ball", "pool", "pocket ball", "cue"],
+ "keywords": [
+ "pool",
+ "billiards",
+ "eight ball",
+ "pool",
+ "pocket ball",
+ "cue"
+ ],
"moji": "🎱"
},
"a": {
@@ -40,7 +64,11 @@
"category": "other",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["alphabet", "letter", "red-square"],
+ "keywords": [
+ "alphabet",
+ "letter",
+ "red-square"
+ ],
"moji": "🅰"
},
"ab": {
@@ -51,7 +79,10 @@
"category": "other",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["alphabet", "red-square"],
+ "keywords": [
+ "alphabet",
+ "red-square"
+ ],
"moji": "🆎"
},
"abc": {
@@ -62,7 +93,10 @@
"category": "other",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["alphabet", "blue-square"],
+ "keywords": [
+ "alphabet",
+ "blue-square"
+ ],
"moji": "🔤"
},
"abcd": {
@@ -73,7 +107,10 @@
"category": "other",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["alphabet", "blue-square"],
+ "keywords": [
+ "alphabet",
+ "blue-square"
+ ],
"moji": "🔡"
},
"accept": {
@@ -84,7 +121,14 @@
"category": "other",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["agree", "chinese", "good", "kanji", "ok", "yes"],
+ "keywords": [
+ "agree",
+ "chinese",
+ "good",
+ "kanji",
+ "ok",
+ "yes"
+ ],
"moji": "🉑"
},
"aerial_tramway": {
@@ -95,18 +139,42 @@
"category": "places",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["transportation", "vehicle", "aerial", "tram", "tramway", "cable", "transport"],
+ "keywords": [
+ "transportation",
+ "vehicle",
+ "aerial",
+ "tram",
+ "tramway",
+ "cable",
+ "transport"
+ ],
"moji": "🚡"
},
"airplane": {
"unicode": "2708",
- "unicode_alternates": ["2708-FE0F"],
+ "unicode_alternates": [
+ "2708-FE0F"
+ ],
"name": "airplane",
"shortname": ":airplane:",
"category": "places",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["flight", "transportation", "vehicle", "airplane", "plane", "airport", "travel", "airlines", "fly", "jet", "jumbo", "boeing", "airbus"],
+ "keywords": [
+ "flight",
+ "transportation",
+ "vehicle",
+ "airplane",
+ "plane",
+ "airport",
+ "travel",
+ "airlines",
+ "fly",
+ "jet",
+ "jumbo",
+ "boeing",
+ "airbus"
+ ],
"moji": "✈"
},
"airplane_arriving": {
@@ -117,7 +185,20 @@
"category": "travel_places",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["flight", "transportation", "vehicle", "plane", "airport", "travel", "airlines", "fly", "jet", "jumbo", "boeing", "airbus"]
+ "keywords": [
+ "flight",
+ "transportation",
+ "vehicle",
+ "plane",
+ "airport",
+ "travel",
+ "airlines",
+ "fly",
+ "jet",
+ "jumbo",
+ "boeing",
+ "airbus"
+ ]
},
"airplane_departure": {
"unicode": "1F6EB",
@@ -127,7 +208,21 @@
"category": "travel_places",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["flight", "transportation", "vehicle", "plane", "airport", "travel", "airlines", "fly", "jet", "jumbo", "boeing", "airbus", "leaving"]
+ "keywords": [
+ "flight",
+ "transportation",
+ "vehicle",
+ "plane",
+ "airport",
+ "travel",
+ "airlines",
+ "fly",
+ "jet",
+ "jumbo",
+ "boeing",
+ "airbus",
+ "leaving"
+ ]
},
"airplane_northeast": {
"unicode": "1F6EA",
@@ -135,9 +230,14 @@
"name": "northeast-pointing airplane",
"shortname": ":airplane_northeast:",
"category": "travel_places",
- "aliases": [":northeast_pointing_airplane:"],
+ "aliases": [
+ ":northeast_pointing_airplane:"
+ ],
"aliases_ascii": [],
- "keywords": ["plane", "travel"]
+ "keywords": [
+ "plane",
+ "travel"
+ ]
},
"airplane_small": {
"unicode": "1F6E9",
@@ -145,9 +245,24 @@
"name": "small airplane",
"shortname": ":airplane_small:",
"category": "travel_places",
- "aliases": [":small_airplane:"],
- "aliases_ascii": [],
- "keywords": ["flight", "transportation", "vehicle", "plane", "airport", "travel", "airlines", "fly", "jet", "jumbo", "boeing", "airbus"]
+ "aliases": [
+ ":small_airplane:"
+ ],
+ "aliases_ascii": [],
+ "keywords": [
+ "flight",
+ "transportation",
+ "vehicle",
+ "plane",
+ "airport",
+ "travel",
+ "airlines",
+ "fly",
+ "jet",
+ "jumbo",
+ "boeing",
+ "airbus"
+ ]
},
"airplane_small_up": {
"unicode": "1F6E8",
@@ -155,9 +270,14 @@
"name": "up-pointing small airplane",
"shortname": ":airplane_small_up:",
"category": "travel_places",
- "aliases": [":up_pointing_small_airplane:"],
+ "aliases": [
+ ":up_pointing_small_airplane:"
+ ],
"aliases_ascii": [],
- "keywords": ["plane", "travel"]
+ "keywords": [
+ "plane",
+ "travel"
+ ]
},
"airplane_up": {
"unicode": "1F6E7",
@@ -165,9 +285,14 @@
"name": "up-pointing airplane",
"shortname": ":airplane_up:",
"category": "travel_places",
- "aliases": [":up_pointing_airplane:"],
+ "aliases": [
+ ":up_pointing_airplane:"
+ ],
"aliases_ascii": [],
- "keywords": ["plane", "travel"]
+ "keywords": [
+ "plane",
+ "travel"
+ ]
},
"alarm_clock": {
"unicode": "23F0",
@@ -177,9 +302,26 @@
"category": "objects",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["time", "wake"],
+ "keywords": [
+ "time",
+ "wake"
+ ],
"moji": "⏰"
},
+ "alembic": {
+ "unicode": "2697",
+ "unicode_alternates": "",
+ "name": "alembic",
+ "shortname": ":alembic:",
+ "category": "objects",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": [
+ "chemistry",
+ "object",
+ "tool"
+ ]
+ },
"alien": {
"unicode": "1F47D",
"unicode_alternates": [],
@@ -188,7 +330,12 @@
"category": "emoticons",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["UFO", "paul", "alien", "ufo"],
+ "keywords": [
+ "UFO",
+ "paul",
+ "alien",
+ "ufo"
+ ],
"moji": "👽"
},
"ambulance": {
@@ -199,18 +346,50 @@
"category": "places",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["911", "health", "ambulance", "emergency", "medical", "help", "assistance"],
+ "keywords": [
+ "911",
+ "health",
+ "ambulance",
+ "emergency",
+ "medical",
+ "help",
+ "assistance"
+ ],
"moji": "🚑"
},
+ "amphora": {
+ "unicode": "1F3FA",
+ "unicode_alternates": "",
+ "name": "amphora",
+ "shortname": ":amphora:",
+ "category": "objects",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": []
+ },
"anchor": {
"unicode": "2693",
- "unicode_alternates": ["2693-FE0F"],
+ "unicode_alternates": [
+ "2693-FE0F"
+ ],
"name": "anchor",
"shortname": ":anchor:",
"category": "places",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["ferry", "ship", "anchor", "ship", "boat", "ocean", "harbor", "marina", "shipyard", "sailor", "tattoo"],
+ "keywords": [
+ "ferry",
+ "ship",
+ "anchor",
+ "ship",
+ "boat",
+ "ocean",
+ "harbor",
+ "marina",
+ "shipyard",
+ "sailor",
+ "tattoo"
+ ],
"moji": "⚓"
},
"angel": {
@@ -221,9 +400,99 @@
"category": "emoticons",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["baby", "angel", "halo", "cupid", "wings", "halo", "heaven", "wings", "jesus"],
+ "keywords": [
+ "baby",
+ "angel",
+ "halo",
+ "cupid",
+ "wings",
+ "halo",
+ "heaven",
+ "wings",
+ "jesus"
+ ],
"moji": "👼"
},
+ "angel_tone1": {
+ "unicode": "1F47C-1F3FB",
+ "unicode_alternates": "",
+ "name": "baby angel tone 1",
+ "shortname": ":angel_tone1:",
+ "category": "people",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": [
+ "halo",
+ "cupid",
+ "heaven",
+ "wings",
+ "jesus"
+ ]
+ },
+ "angel_tone2": {
+ "unicode": "1F47C-1F3FC",
+ "unicode_alternates": "",
+ "name": "baby angel tone 2",
+ "shortname": ":angel_tone2:",
+ "category": "people",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": [
+ "halo",
+ "cupid",
+ "heaven",
+ "wings",
+ "jesus"
+ ]
+ },
+ "angel_tone3": {
+ "unicode": "1F47C-1F3FD",
+ "unicode_alternates": "",
+ "name": "baby angel tone 3",
+ "shortname": ":angel_tone3:",
+ "category": "people",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": [
+ "halo",
+ "cupid",
+ "heaven",
+ "wings",
+ "jesus"
+ ]
+ },
+ "angel_tone4": {
+ "unicode": "1F47C-1F3FE",
+ "unicode_alternates": "",
+ "name": "baby angel tone 4",
+ "shortname": ":angel_tone4:",
+ "category": "people",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": [
+ "halo",
+ "cupid",
+ "heaven",
+ "wings",
+ "jesus"
+ ]
+ },
+ "angel_tone5": {
+ "unicode": "1F47C-1F3FF",
+ "unicode_alternates": "",
+ "name": "baby angel tone 5",
+ "shortname": ":angel_tone5:",
+ "category": "people",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": [
+ "halo",
+ "cupid",
+ "heaven",
+ "wings",
+ "jesus"
+ ]
+ },
"anger": {
"unicode": "1F4A2",
"unicode_alternates": [],
@@ -232,7 +501,11 @@
"category": "emoticons",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["anger", "angry", "mad"],
+ "keywords": [
+ "anger",
+ "angry",
+ "mad"
+ ],
"moji": "💢"
},
"anger_left": {
@@ -241,9 +514,20 @@
"name": "left anger bubble",
"shortname": ":anger_left:",
"category": "objects_symbols",
- "aliases": [":left_anger_bubble:"],
- "aliases_ascii": [],
- "keywords": ["speech", "balloon", "talk", "mood", "conversation", "communication", "comic", "angry"]
+ "aliases": [
+ ":left_anger_bubble:"
+ ],
+ "aliases_ascii": [],
+ "keywords": [
+ "speech",
+ "balloon",
+ "talk",
+ "mood",
+ "conversation",
+ "communication",
+ "comic",
+ "angry"
+ ]
},
"anger_right": {
"unicode": "1F5EF",
@@ -251,9 +535,20 @@
"name": "right anger bubble",
"shortname": ":anger_right:",
"category": "objects_symbols",
- "aliases": [":right_anger_bubble:"],
- "aliases_ascii": [],
- "keywords": ["speech", "balloon", "talk", "mood", "conversation", "communication", "comic", "angry"]
+ "aliases": [
+ ":right_anger_bubble:"
+ ],
+ "aliases_ascii": [],
+ "keywords": [
+ "speech",
+ "balloon",
+ "talk",
+ "mood",
+ "conversation",
+ "communication",
+ "comic",
+ "angry"
+ ]
},
"angry": {
"unicode": "1F620",
@@ -262,8 +557,22 @@
"shortname": ":angry:",
"category": "emoticons",
"aliases": [],
- "aliases_ascii": [">:(", ">:-(", ":@"],
- "keywords": ["angry", "livid", "mad", "vexed", "irritated", "annoyed", "face", "frustrated", "mad"],
+ "aliases_ascii": [
+ ">:(",
+ ">:-(",
+ ":@"
+ ],
+ "keywords": [
+ "angry",
+ "livid",
+ "mad",
+ "vexed",
+ "irritated",
+ "annoyed",
+ "face",
+ "frustrated",
+ "mad"
+ ],
"moji": "😠"
},
"anguished": {
@@ -274,7 +583,17 @@
"category": "emoticons",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["face", "nervous", "stunned", "pain", "anguish", "ouch", "misery", "distress", "grief"],
+ "keywords": [
+ "face",
+ "nervous",
+ "stunned",
+ "pain",
+ "anguish",
+ "ouch",
+ "misery",
+ "distress",
+ "grief"
+ ],
"moji": "😧"
},
"ant": {
@@ -285,7 +604,14 @@
"category": "nature",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["animal", "insect", "ant", "queen", "insect", "team"],
+ "keywords": [
+ "animal",
+ "insect",
+ "ant",
+ "queen",
+ "insect",
+ "team"
+ ],
"moji": "🐜"
},
"apple": {
@@ -296,40 +622,87 @@
"category": "objects",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["fruit", "mac", "apple", "fruit", "electronics", "red", "doctor", "teacher", "school", "core"],
+ "keywords": [
+ "fruit",
+ "mac",
+ "apple",
+ "fruit",
+ "electronics",
+ "red",
+ "doctor",
+ "teacher",
+ "school",
+ "core"
+ ],
"moji": "🍎"
},
"aquarius": {
"unicode": "2652",
- "unicode_alternates": ["2652-FE0F"],
+ "unicode_alternates": [
+ "2652-FE0F"
+ ],
"name": "aquarius",
"shortname": ":aquarius:",
"category": "other",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["aquarius", "water", "bearer", "astrology", "greek", "constellation", "stars", "zodiac", "sign", "purple-square", "sign", "zodiac", "horoscope"],
+ "keywords": [
+ "aquarius",
+ "water",
+ "bearer",
+ "astrology",
+ "greek",
+ "constellation",
+ "stars",
+ "zodiac",
+ "sign",
+ "purple-square",
+ "sign",
+ "zodiac",
+ "horoscope"
+ ],
"moji": "♒"
},
"aries": {
"unicode": "2648",
- "unicode_alternates": ["2648-FE0F"],
+ "unicode_alternates": [
+ "2648-FE0F"
+ ],
"name": "aries",
"shortname": ":aries:",
"category": "other",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["aries", "ram", "astrology", "greek", "constellation", "stars", "zodiac", "sign", "purple-square", "sign", "zodiac", "horoscope"],
+ "keywords": [
+ "aries",
+ "ram",
+ "astrology",
+ "greek",
+ "constellation",
+ "stars",
+ "zodiac",
+ "sign",
+ "purple-square",
+ "sign",
+ "zodiac",
+ "horoscope"
+ ],
"moji": "♈"
},
"arrow_backward": {
"unicode": "25C0",
- "unicode_alternates": ["25C0-FE0F"],
+ "unicode_alternates": [
+ "25C0-FE0F"
+ ],
"name": "black left-pointing triangle",
"shortname": ":arrow_backward:",
"category": "other",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["arrow", "blue-square"],
+ "keywords": [
+ "arrow",
+ "blue-square"
+ ],
"moji": "◀"
},
"arrow_double_down": {
@@ -340,7 +713,10 @@
"category": "other",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["arrow", "blue-square"],
+ "keywords": [
+ "arrow",
+ "blue-square"
+ ],
"moji": "⏬"
},
"arrow_double_up": {
@@ -351,18 +727,26 @@
"category": "other",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["arrow", "blue-square"],
+ "keywords": [
+ "arrow",
+ "blue-square"
+ ],
"moji": "⏫"
},
"arrow_down": {
"unicode": "2B07",
- "unicode_alternates": ["2B07-FE0F"],
+ "unicode_alternates": [
+ "2B07-FE0F"
+ ],
"name": "downwards black arrow",
"shortname": ":arrow_down:",
"category": "other",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["arrow", "blue-square"],
+ "keywords": [
+ "arrow",
+ "blue-square"
+ ],
"moji": "⬇"
},
"arrow_down_small": {
@@ -373,117 +757,168 @@
"category": "other",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["arrow", "blue-square"],
+ "keywords": [
+ "arrow",
+ "blue-square"
+ ],
"moji": "🔽"
},
"arrow_forward": {
"unicode": "25B6",
- "unicode_alternates": ["25B6-FE0F"],
+ "unicode_alternates": [
+ "25B6-FE0F"
+ ],
"name": "black right-pointing triangle",
"shortname": ":arrow_forward:",
"category": "other",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["arrow", "blue-square"],
+ "keywords": [
+ "arrow",
+ "blue-square"
+ ],
"moji": "▶"
},
"arrow_heading_down": {
"unicode": "2935",
- "unicode_alternates": ["2935-FE0F"],
+ "unicode_alternates": [
+ "2935-FE0F"
+ ],
"name": "arrow pointing rightwards then curving downwards",
"shortname": ":arrow_heading_down:",
"category": "other",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["arrow", "blue-square"],
+ "keywords": [
+ "arrow",
+ "blue-square"
+ ],
"moji": "⤵"
},
"arrow_heading_up": {
"unicode": "2934",
- "unicode_alternates": ["2934-FE0F"],
+ "unicode_alternates": [
+ "2934-FE0F"
+ ],
"name": "arrow pointing rightwards then curving upwards",
"shortname": ":arrow_heading_up:",
"category": "other",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["arrow", "blue-square"],
+ "keywords": [
+ "arrow",
+ "blue-square"
+ ],
"moji": "⤴"
},
"arrow_left": {
"unicode": "2B05",
- "unicode_alternates": ["2B05-FE0F"],
+ "unicode_alternates": [
+ "2B05-FE0F"
+ ],
"name": "leftwards black arrow",
"shortname": ":arrow_left:",
"category": "other",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["arrow", "blue-square", "previous"],
+ "keywords": [
+ "arrow",
+ "blue-square",
+ "previous"
+ ],
"moji": "⬅"
},
"arrow_lower_left": {
"unicode": "2199",
- "unicode_alternates": ["2199-FE0F"],
+ "unicode_alternates": [
+ "2199-FE0F"
+ ],
"name": "south west arrow",
"shortname": ":arrow_lower_left:",
"category": "other",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["arrow", "blue-square"],
+ "keywords": [
+ "arrow",
+ "blue-square"
+ ],
"moji": "↙"
},
"arrow_lower_right": {
"unicode": "2198",
- "unicode_alternates": ["2198-FE0F"],
+ "unicode_alternates": [
+ "2198-FE0F"
+ ],
"name": "south east arrow",
"shortname": ":arrow_lower_right:",
"category": "other",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["arrow", "blue-square"],
+ "keywords": [
+ "arrow",
+ "blue-square"
+ ],
"moji": "↘"
},
"arrow_right": {
"unicode": "27A1",
- "unicode_alternates": ["27A1-FE0F"],
+ "unicode_alternates": [
+ "27A1-FE0F"
+ ],
"name": "black rightwards arrow",
"shortname": ":arrow_right:",
"category": "other",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["blue-square", "next"],
+ "keywords": [
+ "blue-square",
+ "next"
+ ],
"moji": "➡"
},
"arrow_right_hook": {
"unicode": "21AA",
- "unicode_alternates": ["21AA-FE0F"],
+ "unicode_alternates": [
+ "21AA-FE0F"
+ ],
"name": "rightwards arrow with hook",
"shortname": ":arrow_right_hook:",
"category": "other",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["blue-square"],
+ "keywords": [
+ "blue-square"
+ ],
"moji": "↪"
},
"arrow_up": {
"unicode": "2B06",
- "unicode_alternates": ["2B06-FE0F"],
+ "unicode_alternates": [
+ "2B06-FE0F"
+ ],
"name": "upwards black arrow",
"shortname": ":arrow_up:",
"category": "other",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["blue-square"],
+ "keywords": [
+ "blue-square"
+ ],
"moji": "⬆"
},
"arrow_up_down": {
"unicode": "2195",
- "unicode_alternates": ["2195-FE0F"],
+ "unicode_alternates": [
+ "2195-FE0F"
+ ],
"name": "up down arrow",
"shortname": ":arrow_up_down:",
"category": "other",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["blue-square"],
+ "keywords": [
+ "blue-square"
+ ],
"moji": "↕"
},
"arrow_up_small": {
@@ -494,29 +929,39 @@
"category": "other",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["blue-square"],
+ "keywords": [
+ "blue-square"
+ ],
"moji": "🔼"
},
"arrow_upper_left": {
"unicode": "2196",
- "unicode_alternates": ["2196-FE0F"],
+ "unicode_alternates": [
+ "2196-FE0F"
+ ],
"name": "north west arrow",
"shortname": ":arrow_upper_left:",
"category": "other",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["blue-square"],
+ "keywords": [
+ "blue-square"
+ ],
"moji": "↖"
},
"arrow_upper_right": {
"unicode": "2197",
- "unicode_alternates": ["2197-FE0F"],
+ "unicode_alternates": [
+ "2197-FE0F"
+ ],
"name": "north east arrow",
"shortname": ":arrow_upper_right:",
"category": "other",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["blue-square"],
+ "keywords": [
+ "blue-square"
+ ],
"moji": "↗"
},
"arrows_clockwise": {
@@ -527,7 +972,9 @@
"category": "other",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["sync"],
+ "keywords": [
+ "sync"
+ ],
"moji": "🔃"
},
"arrows_counterclockwise": {
@@ -538,7 +985,10 @@
"category": "other",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["blue-square", "sync"],
+ "keywords": [
+ "blue-square",
+ "sync"
+ ],
"moji": "🔄"
},
"art": {
@@ -549,7 +999,20 @@
"category": "objects",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["design", "draw", "paint", "artist", "palette", "art", "colors", "paint", "draw", "brush", "pastels", "oils"],
+ "keywords": [
+ "design",
+ "draw",
+ "paint",
+ "artist",
+ "palette",
+ "art",
+ "colors",
+ "paint",
+ "draw",
+ "brush",
+ "pastels",
+ "oils"
+ ],
"moji": "🎨"
},
"articulated_lorry": {
@@ -560,7 +1023,16 @@
"category": "places",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["cars", "transportation", "vehicle", "truck", "delivery", "semi", "lorry", "articulated"],
+ "keywords": [
+ "cars",
+ "transportation",
+ "vehicle",
+ "truck",
+ "delivery",
+ "semi",
+ "lorry",
+ "articulated"
+ ],
"moji": "🚛"
},
"ascending_notes": {
@@ -571,7 +1043,28 @@
"category": "objects_symbols",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["score", "music", "sound", "tone"]
+ "keywords": [
+ "score",
+ "music",
+ "sound",
+ "tone"
+ ]
+ },
+ "asterisk": {
+ "unicode": "002A-20E3",
+ "unicode_alternates": "002a-fe0f-20e3",
+ "name": "keycap asterisk",
+ "shortname": ":asterisk:",
+ "category": "symbols",
+ "aliases": [
+ ":keycap_asterisk:"
+ ],
+ "aliases_ascii": [],
+ "keywords": [
+ "*",
+ "star",
+ "symbol"
+ ]
},
"astonished": {
"unicode": "1F632",
@@ -581,7 +1074,13 @@
"category": "emoticons",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["face", "xox", "shocked", "surprise", "astonished"],
+ "keywords": [
+ "face",
+ "xox",
+ "shocked",
+ "surprise",
+ "astonished"
+ ],
"moji": "😲"
},
"athletic_shoe": {
@@ -592,7 +1091,10 @@
"category": "emoticons",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["shoes", "sports"],
+ "keywords": [
+ "shoes",
+ "sports"
+ ],
"moji": "👟"
},
"atm": {
@@ -603,9 +1105,38 @@
"category": "other",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["atm", "cash", "withdrawal", "money", "deposit", "financial", "bank", "adam", "payday", "bank", "blue-square", "cash", "money", "payment"],
+ "keywords": [
+ "atm",
+ "cash",
+ "withdrawal",
+ "money",
+ "deposit",
+ "financial",
+ "bank",
+ "adam",
+ "payday",
+ "bank",
+ "blue-square",
+ "cash",
+ "money",
+ "payment"
+ ],
"moji": "🏧"
},
+ "atom": {
+ "unicode": "269B",
+ "unicode_alternates": "",
+ "name": "atom symbol",
+ "shortname": ":atom:",
+ "category": "symbols",
+ "aliases": [
+ ":atom_symbol:"
+ ],
+ "aliases_ascii": [],
+ "keywords": [
+ "atheist"
+ ]
+ },
"b": {
"unicode": "1F171",
"unicode_alternates": [],
@@ -614,7 +1145,11 @@
"category": "other",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["alphabet", "letter", "red-square"],
+ "keywords": [
+ "alphabet",
+ "letter",
+ "red-square"
+ ],
"moji": "🅱"
},
"baby": {
@@ -625,7 +1160,11 @@
"category": "emoticons",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["boy", "child", "infant"],
+ "keywords": [
+ "boy",
+ "child",
+ "infant"
+ ],
"moji": "👶"
},
"baby_bottle": {
@@ -636,7 +1175,17 @@
"category": "objects",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["container", "food", "baby", "bottle", "milk", "mother", "nipple", "newborn", "formula"],
+ "keywords": [
+ "container",
+ "food",
+ "baby",
+ "bottle",
+ "milk",
+ "mother",
+ "nipple",
+ "newborn",
+ "formula"
+ ],
"moji": "🍼"
},
"baby_chick": {
@@ -647,7 +1196,17 @@
"category": "nature",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["animal", "chicken", "chick", "baby", "bird", "chicken", "young", "woman", "cute"],
+ "keywords": [
+ "animal",
+ "chicken",
+ "chick",
+ "baby",
+ "bird",
+ "chicken",
+ "young",
+ "woman",
+ "cute"
+ ],
"moji": "🐤"
},
"baby_symbol": {
@@ -658,9 +1217,89 @@
"category": "other",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["child", "orange-square", "baby", "crawl", "newborn", "human", "diaper", "small", "babe"],
+ "keywords": [
+ "child",
+ "orange-square",
+ "baby",
+ "crawl",
+ "newborn",
+ "human",
+ "diaper",
+ "small",
+ "babe"
+ ],
"moji": "🚼"
},
+ "baby_tone1": {
+ "unicode": "1F476-1F3FB",
+ "unicode_alternates": "",
+ "name": "baby tone 1",
+ "shortname": ":baby_tone1:",
+ "category": "people",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": [
+ "child",
+ "infant",
+ "toddler"
+ ]
+ },
+ "baby_tone2": {
+ "unicode": "1F476-1F3FC",
+ "unicode_alternates": "",
+ "name": "baby tone 2",
+ "shortname": ":baby_tone2:",
+ "category": "people",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": [
+ "child",
+ "infant",
+ "toddler"
+ ]
+ },
+ "baby_tone3": {
+ "unicode": "1F476-1F3FD",
+ "unicode_alternates": "",
+ "name": "baby tone 3",
+ "shortname": ":baby_tone3:",
+ "category": "people",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": [
+ "child",
+ "infant",
+ "toddler"
+ ]
+ },
+ "baby_tone4": {
+ "unicode": "1F476-1F3FE",
+ "unicode_alternates": "",
+ "name": "baby tone 4",
+ "shortname": ":baby_tone4:",
+ "category": "people",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": [
+ "child",
+ "infant",
+ "toddler"
+ ]
+ },
+ "baby_tone5": {
+ "unicode": "1F476-1F3FF",
+ "unicode_alternates": "",
+ "name": "baby tone 5",
+ "shortname": ":baby_tone5:",
+ "category": "people",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": [
+ "child",
+ "infant",
+ "toddler"
+ ]
+ },
"back": {
"unicode": "1F519",
"unicode_alternates": [],
@@ -669,9 +1308,21 @@
"category": "other",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["arrow"],
+ "keywords": [
+ "arrow"
+ ],
"moji": "🔙"
},
+ "badminton": {
+ "unicode": "1F3F8",
+ "unicode_alternates": "",
+ "name": "badminton racquet",
+ "shortname": ":badminton:",
+ "category": "activity",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": []
+ },
"baggage_claim": {
"unicode": "1F6C4",
"unicode_alternates": [],
@@ -680,7 +1331,15 @@
"category": "other",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["airport", "blue-square", "transport", "bag", "baggage", "luggage", "travel"],
+ "keywords": [
+ "airport",
+ "blue-square",
+ "transport",
+ "bag",
+ "baggage",
+ "luggage",
+ "travel"
+ ],
"moji": "🛄"
},
"balloon": {
@@ -691,7 +1350,17 @@
"category": "objects",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["celebration", "party", "balloon", "birthday", "celebration", "helium", "gas", "children", "float"],
+ "keywords": [
+ "celebration",
+ "party",
+ "balloon",
+ "birthday",
+ "celebration",
+ "helium",
+ "gas",
+ "children",
+ "float"
+ ],
"moji": "🎈"
},
"ballot_box": {
@@ -700,9 +1369,13 @@
"name": "ballot box with ballot",
"shortname": ":ballot_box:",
"category": "objects_symbols",
- "aliases": [":ballot_box_with_ballot:"],
+ "aliases": [
+ ":ballot_box_with_ballot:"
+ ],
"aliases_ascii": [],
- "keywords": ["vote"]
+ "keywords": [
+ "vote"
+ ]
},
"ballot_box_check": {
"unicode": "1F5F9",
@@ -710,19 +1383,29 @@
"name": "ballot box with bold check",
"shortname": ":ballot_box_check:",
"category": "objects_symbols",
- "aliases": [":ballot_box_with_bold_check:"],
+ "aliases": [
+ ":ballot_box_with_bold_check:"
+ ],
"aliases_ascii": [],
- "keywords": ["mark", "vote"]
+ "keywords": [
+ "mark",
+ "vote"
+ ]
},
"ballot_box_with_check": {
"unicode": "2611",
- "unicode_alternates": ["2611-FE0F"],
+ "unicode_alternates": [
+ "2611-FE0F"
+ ],
"name": "ballot box with check",
"shortname": ":ballot_box_with_check:",
"category": "other",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["agree", "ok"],
+ "keywords": [
+ "agree",
+ "ok"
+ ],
"moji": "☑"
},
"ballot_box_x": {
@@ -731,9 +1414,14 @@
"name": "ballot box with script x",
"shortname": ":ballot_box_x:",
"category": "objects_symbols",
- "aliases": [":ballot_box_with_script_x:"],
+ "aliases": [
+ ":ballot_box_with_script_x:"
+ ],
"aliases_ascii": [],
- "keywords": ["mark", "vote"]
+ "keywords": [
+ "mark",
+ "vote"
+ ]
},
"ballot_x": {
"unicode": "1F5F4",
@@ -741,9 +1429,14 @@
"name": "ballot script x",
"shortname": ":ballot_x:",
"category": "objects_symbols",
- "aliases": [":ballot_script_x:"],
+ "aliases": [
+ ":ballot_script_x:"
+ ],
"aliases_ascii": [],
- "keywords": ["mark", "vote"]
+ "keywords": [
+ "mark",
+ "vote"
+ ]
},
"bamboo": {
"unicode": "1F38D",
@@ -753,7 +1446,25 @@
"category": "objects",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["nature", "plant", "vegetable", "pine", "bamboo", "decoration", "new", "years", "spirits", "harvest", "prosperity", "longevity", "fortune", "luck", "welcome", "farming", "agriculture"],
+ "keywords": [
+ "nature",
+ "plant",
+ "vegetable",
+ "pine",
+ "bamboo",
+ "decoration",
+ "new",
+ "years",
+ "spirits",
+ "harvest",
+ "prosperity",
+ "longevity",
+ "fortune",
+ "luck",
+ "welcome",
+ "farming",
+ "agriculture"
+ ],
"moji": "🎍"
},
"banana": {
@@ -764,18 +1475,29 @@
"category": "objects",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["food", "fruit", "banana", "peel", "bunch"],
+ "keywords": [
+ "food",
+ "fruit",
+ "banana",
+ "peel",
+ "bunch"
+ ],
"moji": "🍌"
},
"bangbang": {
"unicode": "203C",
- "unicode_alternates": ["203C-FE0F"],
+ "unicode_alternates": [
+ "203C-FE0F"
+ ],
"name": "double exclamation mark",
"shortname": ":bangbang:",
"category": "other",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["exclamation", "surprise"],
+ "keywords": [
+ "exclamation",
+ "surprise"
+ ],
"moji": "‼"
},
"bank": {
@@ -786,7 +1508,9 @@
"category": "places",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["building"],
+ "keywords": [
+ "building"
+ ],
"moji": "🏦"
},
"bar_chart": {
@@ -797,7 +1521,11 @@
"category": "objects",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["graph", "presentation", "stats"],
+ "keywords": [
+ "graph",
+ "presentation",
+ "stats"
+ ],
"moji": "📊"
},
"barber": {
@@ -808,18 +1536,28 @@
"category": "places",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["hair", "salon", "style"],
+ "keywords": [
+ "hair",
+ "salon",
+ "style"
+ ],
"moji": "💈"
},
"baseball": {
"unicode": "26BE",
- "unicode_alternates": ["26BE-FE0F"],
+ "unicode_alternates": [
+ "26BE-FE0F"
+ ],
"name": "baseball",
"shortname": ":baseball:",
"category": "objects",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["MLB", "balls", "sports"],
+ "keywords": [
+ "MLB",
+ "balls",
+ "sports"
+ ],
"moji": "⚾"
},
"basketball": {
@@ -830,9 +1568,95 @@
"category": "objects",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["NBA", "balls", "sports", "basketball", "bball", "dribble", "hoop", "net", "swish", "rip city"],
+ "keywords": [
+ "NBA",
+ "balls",
+ "sports",
+ "basketball",
+ "bball",
+ "dribble",
+ "hoop",
+ "net",
+ "swish",
+ "rip city"
+ ],
"moji": "🏀"
},
+ "basketball_player": {
+ "unicode": "26F9",
+ "unicode_alternates": "",
+ "name": "person with ball",
+ "shortname": ":basketball_player:",
+ "category": "activity",
+ "aliases": [
+ ":person_with_ball:"
+ ],
+ "aliases_ascii": [],
+ "keywords": [
+ "sport",
+ "travel"
+ ]
+ },
+ "basketball_player_tone1": {
+ "unicode": "26F9-1F3FB",
+ "unicode_alternates": "",
+ "name": "person with ball tone 1",
+ "shortname": ":basketball_player_tone1:",
+ "category": "activity",
+ "aliases": [
+ ":person_with_ball_tone1:"
+ ],
+ "aliases_ascii": [],
+ "keywords": []
+ },
+ "basketball_player_tone2": {
+ "unicode": "26F9-1F3FC",
+ "unicode_alternates": "",
+ "name": "person with ball tone 2",
+ "shortname": ":basketball_player_tone2:",
+ "category": "activity",
+ "aliases": [
+ ":person_with_ball_tone2:"
+ ],
+ "aliases_ascii": [],
+ "keywords": []
+ },
+ "basketball_player_tone3": {
+ "unicode": "26F9-1F3FD",
+ "unicode_alternates": "",
+ "name": "person with ball tone 3",
+ "shortname": ":basketball_player_tone3:",
+ "category": "activity",
+ "aliases": [
+ ":person_with_ball_tone3:"
+ ],
+ "aliases_ascii": [],
+ "keywords": []
+ },
+ "basketball_player_tone4": {
+ "unicode": "26F9-1F3FE",
+ "unicode_alternates": "",
+ "name": "person with ball tone 4",
+ "shortname": ":basketball_player_tone4:",
+ "category": "activity",
+ "aliases": [
+ ":person_with_ball_tone4:"
+ ],
+ "aliases_ascii": [],
+ "keywords": []
+ },
+ "basketball_player_tone5": {
+ "unicode": "26F9-1F3FF",
+ "unicode_alternates": "",
+ "name": "person with ball tone 5",
+ "shortname": ":basketball_player_tone5:",
+ "category": "activity",
+ "aliases": [
+ ":person_with_ball_tone5:"
+ ],
+ "aliases_ascii": [],
+ "keywords": []
+ },
"bath": {
"unicode": "1F6C0",
"unicode_alternates": [],
@@ -841,9 +1665,140 @@
"category": "objects",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["clean", "shower", "bath", "tub", "basin", "wash", "bubble", "soak", "bathroom", "soap", "water", "clean", "shampoo", "lather", "water"],
+ "keywords": [
+ "clean",
+ "shower",
+ "bath",
+ "tub",
+ "basin",
+ "wash",
+ "bubble",
+ "soak",
+ "bathroom",
+ "soap",
+ "water",
+ "clean",
+ "shampoo",
+ "lather",
+ "water"
+ ],
"moji": "🛀"
},
+ "bath_tone1": {
+ "unicode": "1F6C0-1F3FB",
+ "unicode_alternates": "",
+ "name": "bath tone 1",
+ "shortname": ":bath_tone1:",
+ "category": "activity",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": [
+ "shower",
+ "tub",
+ "basin",
+ "wash",
+ "bubble",
+ "soak",
+ "bathroom",
+ "soap",
+ "water",
+ "clean",
+ "shampoo",
+ "lather"
+ ]
+ },
+ "bath_tone2": {
+ "unicode": "1F6C0-1F3FC",
+ "unicode_alternates": "",
+ "name": "bath tone 2",
+ "shortname": ":bath_tone2:",
+ "category": "activity",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": [
+ "shower",
+ "tub",
+ "basin",
+ "wash",
+ "bubble",
+ "soak",
+ "bathroom",
+ "soap",
+ "water",
+ "clean",
+ "shampoo",
+ "lather"
+ ]
+ },
+ "bath_tone3": {
+ "unicode": "1F6C0-1F3FD",
+ "unicode_alternates": "",
+ "name": "bath tone 3",
+ "shortname": ":bath_tone3:",
+ "category": "activity",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": [
+ "shower",
+ "tub",
+ "basin",
+ "wash",
+ "bubble",
+ "soak",
+ "bathroom",
+ "soap",
+ "water",
+ "clean",
+ "shampoo",
+ "lather"
+ ]
+ },
+ "bath_tone4": {
+ "unicode": "1F6C0-1F3FE",
+ "unicode_alternates": "",
+ "name": "bath tone 4",
+ "shortname": ":bath_tone4:",
+ "category": "activity",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": [
+ "shower",
+ "tub",
+ "basin",
+ "wash",
+ "bubble",
+ "soak",
+ "bathroom",
+ "soap",
+ "water",
+ "clean",
+ "shampoo",
+ "lather"
+ ]
+ },
+ "bath_tone5": {
+ "unicode": "1F6C0-1F3FF",
+ "unicode_alternates": "",
+ "name": "bath tone 5",
+ "shortname": ":bath_tone5:",
+ "category": "activity",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": [
+ "shower",
+ "tub",
+ "basin",
+ "wash",
+ "bubble",
+ "soak",
+ "bathroom",
+ "soap",
+ "water",
+ "clean",
+ "shampoo",
+ "lather"
+ ]
+ },
"bathtub": {
"unicode": "1F6C1",
"unicode_alternates": [],
@@ -852,7 +1807,23 @@
"category": "objects",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["clean", "shower", "bath", "tub", "basin", "wash", "bubble", "soak", "bathroom", "soap", "water", "clean", "shampoo", "lather", "water"],
+ "keywords": [
+ "clean",
+ "shower",
+ "bath",
+ "tub",
+ "basin",
+ "wash",
+ "bubble",
+ "soak",
+ "bathroom",
+ "soap",
+ "water",
+ "clean",
+ "shampoo",
+ "lather",
+ "water"
+ ],
"moji": "🛁"
},
"battery": {
@@ -863,7 +1834,11 @@
"category": "objects",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["energy", "power", "sustain"],
+ "keywords": [
+ "energy",
+ "power",
+ "sustain"
+ ],
"moji": "🔋"
},
"beach": {
@@ -872,9 +1847,38 @@
"name": "beach with umbrella",
"shortname": ":beach:",
"category": "travel_places",
- "aliases": [":beach_with_umbrella:"],
- "aliases_ascii": [],
- "keywords": ["sand", "sun", "surf", "vacation", "relaxation", "tanning", "tan", "swimming"]
+ "aliases": [
+ ":beach_with_umbrella:"
+ ],
+ "aliases_ascii": [],
+ "keywords": [
+ "sand",
+ "sun",
+ "surf",
+ "vacation",
+ "relaxation",
+ "tanning",
+ "tan",
+ "swimming"
+ ]
+ },
+ "beach_umbrella": {
+ "unicode": "26F1",
+ "unicode_alternates": "",
+ "name": "umbrella on ground",
+ "shortname": ":beach_umbrella:",
+ "category": "objects",
+ "aliases": [
+ ":umbrella_on_ground:"
+ ],
+ "aliases_ascii": [],
+ "keywords": [
+ "nature",
+ "rain",
+ "sun",
+ "travel",
+ "weather"
+ ]
},
"bear": {
"unicode": "1F43B",
@@ -884,7 +1888,10 @@
"category": "nature",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["animal", "nature"],
+ "keywords": [
+ "animal",
+ "nature"
+ ],
"moji": "🐻"
},
"bed": {
@@ -895,7 +1902,15 @@
"category": "travel_places",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["sleep", "sex", "queen", "full", "twin", "king", "mattress"]
+ "keywords": [
+ "sleep",
+ "sex",
+ "queen",
+ "full",
+ "twin",
+ "king",
+ "mattress"
+ ]
},
"bee": {
"unicode": "1F41D",
@@ -905,7 +1920,20 @@
"category": "nature",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["animal", "insect", "bee", "queen", "buzz", "flower", "pollen", "sting", "honey", "hive", "bumble", "pollination"],
+ "keywords": [
+ "animal",
+ "insect",
+ "bee",
+ "queen",
+ "buzz",
+ "flower",
+ "pollen",
+ "sting",
+ "honey",
+ "hive",
+ "bumble",
+ "pollination"
+ ],
"moji": "🐝"
},
"beer": {
@@ -916,7 +1944,26 @@
"category": "objects",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["beverage", "drink", "drunk", "party", "pub", "relax", "beer", "hops", "mug", "barley", "malt", "yeast", "portland", "oregon", "brewery", "micro", "pint", "boot"],
+ "keywords": [
+ "beverage",
+ "drink",
+ "drunk",
+ "party",
+ "pub",
+ "relax",
+ "beer",
+ "hops",
+ "mug",
+ "barley",
+ "malt",
+ "yeast",
+ "portland",
+ "oregon",
+ "brewery",
+ "micro",
+ "pint",
+ "boot"
+ ],
"moji": "🍺"
},
"beers": {
@@ -927,7 +1974,25 @@
"category": "objects",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["beverage", "drink", "drunk", "party", "pub", "relax", "beer", "beers", "cheers", "mug", "toast", "celebrate", "pub", "bar", "jolly", "hops", "clink"],
+ "keywords": [
+ "beverage",
+ "drink",
+ "drunk",
+ "party",
+ "pub",
+ "relax",
+ "beer",
+ "beers",
+ "cheers",
+ "mug",
+ "toast",
+ "celebrate",
+ "pub",
+ "bar",
+ "jolly",
+ "hops",
+ "clink"
+ ],
"moji": "🍻"
},
"beetle": {
@@ -938,7 +2003,19 @@
"category": "nature",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["insect", "nature", "lady", "bug", "ladybug", "ladybird", "beetle", "cow", "lady cow", "insect", "endearment"],
+ "keywords": [
+ "insect",
+ "nature",
+ "lady",
+ "bug",
+ "ladybug",
+ "ladybird",
+ "beetle",
+ "cow",
+ "lady cow",
+ "insect",
+ "endearment"
+ ],
"moji": "🐞"
},
"beginner": {
@@ -949,7 +2026,10 @@
"category": "places",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["badge", "shield"],
+ "keywords": [
+ "badge",
+ "shield"
+ ],
"moji": "🔰"
},
"bell": {
@@ -960,7 +2040,13 @@
"category": "objects",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["chime", "christmas", "notification", "sound", "xmas"],
+ "keywords": [
+ "chime",
+ "christmas",
+ "notification",
+ "sound",
+ "xmas"
+ ],
"moji": "🔔"
},
"bellhop": {
@@ -969,9 +2055,15 @@
"name": "bellhop bell",
"shortname": ":bellhop:",
"category": "travel_places",
- "aliases": [":bellhop_bell:"],
+ "aliases": [
+ ":bellhop_bell:"
+ ],
"aliases_ascii": [],
- "keywords": ["hotel", "porter", "ding"]
+ "keywords": [
+ "hotel",
+ "porter",
+ "ding"
+ ]
},
"bento": {
"unicode": "1F371",
@@ -981,7 +2073,19 @@
"category": "objects",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["box", "food", "japanese", "bento", "japanese", "rice", "meal", "box", "obento", "convenient", "lunchbox"],
+ "keywords": [
+ "box",
+ "food",
+ "japanese",
+ "bento",
+ "japanese",
+ "rice",
+ "meal",
+ "box",
+ "obento",
+ "convenient",
+ "lunchbox"
+ ],
"moji": "🍱"
},
"bicyclist": {
@@ -992,9 +2096,115 @@
"category": "objects",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["bike", "exercise", "hipster", "sports", "bicyclist", "road", "bike", "pedal", "bicycle", "transportation"],
+ "keywords": [
+ "bike",
+ "exercise",
+ "hipster",
+ "sports",
+ "bicyclist",
+ "road",
+ "bike",
+ "pedal",
+ "bicycle",
+ "transportation"
+ ],
"moji": "🚴"
},
+ "bicyclist_tone1": {
+ "unicode": "1F6B4-1F3FB",
+ "unicode_alternates": "",
+ "name": "bicyclist tone 1",
+ "shortname": ":bicyclist_tone1:",
+ "category": "activity",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": [
+ "bike",
+ "exercise",
+ "hipster",
+ "sport",
+ "road",
+ "pedal",
+ "bicycle",
+ "transportation"
+ ]
+ },
+ "bicyclist_tone2": {
+ "unicode": "1F6B4-1F3FC",
+ "unicode_alternates": "",
+ "name": "bicyclist tone 2",
+ "shortname": ":bicyclist_tone2:",
+ "category": "activity",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": [
+ "bike",
+ "exercise",
+ "hipster",
+ "sport",
+ "road",
+ "pedal",
+ "bicycle",
+ "transportation"
+ ]
+ },
+ "bicyclist_tone3": {
+ "unicode": "1F6B4-1F3FD",
+ "unicode_alternates": "",
+ "name": "bicyclist tone 3",
+ "shortname": ":bicyclist_tone3:",
+ "category": "activity",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": [
+ "bike",
+ "exercise",
+ "hipster",
+ "sport",
+ "road",
+ "pedal",
+ "bicycle",
+ "transportation"
+ ]
+ },
+ "bicyclist_tone4": {
+ "unicode": "1F6B4-1F3FE",
+ "unicode_alternates": "",
+ "name": "bicyclist tone 4",
+ "shortname": ":bicyclist_tone4:",
+ "category": "activity",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": [
+ "bike",
+ "exercise",
+ "hipster",
+ "sport",
+ "road",
+ "pedal",
+ "bicycle",
+ "transportation"
+ ]
+ },
+ "bicyclist_tone5": {
+ "unicode": "1F6B4-1F3FF",
+ "unicode_alternates": "",
+ "name": "bicyclist tone 5",
+ "shortname": ":bicyclist_tone5:",
+ "category": "activity",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": [
+ "bike",
+ "exercise",
+ "hipster",
+ "sport",
+ "road",
+ "pedal",
+ "bicycle",
+ "transportation"
+ ]
+ },
"bike": {
"unicode": "1F6B2",
"unicode_alternates": [],
@@ -1003,7 +2213,16 @@
"category": "places",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["bicycle", "exercise", "hipster", "sports", "bike", "pedal", "bicycle", "transportation"],
+ "keywords": [
+ "bicycle",
+ "exercise",
+ "hipster",
+ "sports",
+ "bike",
+ "pedal",
+ "bicycle",
+ "transportation"
+ ],
"moji": "🚲"
},
"bikini": {
@@ -1014,9 +2233,30 @@
"category": "emoticons",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["beach", "fashion", "female", "girl", "swimming", "woman"],
+ "keywords": [
+ "beach",
+ "fashion",
+ "female",
+ "girl",
+ "swimming",
+ "woman"
+ ],
"moji": "👙"
},
+ "biohazard": {
+ "unicode": "2623",
+ "unicode_alternates": "",
+ "name": "biohazard sign",
+ "shortname": ":biohazard:",
+ "category": "symbols",
+ "aliases": [
+ ":biohazard_sign:"
+ ],
+ "aliases_ascii": [],
+ "keywords": [
+ "symbol"
+ ]
+ },
"bird": {
"unicode": "1F426",
"unicode_alternates": [],
@@ -1025,7 +2265,12 @@
"category": "nature",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["animal", "fly", "nature", "tweet"],
+ "keywords": [
+ "animal",
+ "fly",
+ "nature",
+ "tweet"
+ ],
"moji": "🐦"
},
"birthday": {
@@ -1036,18 +2281,31 @@
"category": "objects",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["cake", "party", "birthday", "birth", "cake", "dessert", "wish", "celebrate"],
+ "keywords": [
+ "cake",
+ "party",
+ "birthday",
+ "birth",
+ "cake",
+ "dessert",
+ "wish",
+ "celebrate"
+ ],
"moji": "🎂"
},
"black_circle": {
"unicode": "26AB",
- "unicode_alternates": ["26AB-FE0F"],
+ "unicode_alternates": [
+ "26AB-FE0F"
+ ],
"name": "medium black circle",
"shortname": ":black_circle:",
"category": "other",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["shape"],
+ "keywords": [
+ "shape"
+ ],
"moji": "⚫"
},
"black_joker": {
@@ -1058,23 +2316,33 @@
"category": "objects",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["cards", "game", "poker"],
+ "keywords": [
+ "cards",
+ "game",
+ "poker"
+ ],
"moji": "🃏"
},
"black_large_square": {
"unicode": "2B1B",
- "unicode_alternates": ["2B1B-FE0F"],
+ "unicode_alternates": [
+ "2B1B-FE0F"
+ ],
"name": "black large square",
"shortname": ":black_large_square:",
"category": "other",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["shape"],
+ "keywords": [
+ "shape"
+ ],
"moji": "⬛"
},
"black_medium_small_square": {
"unicode": "25FE",
- "unicode_alternates": ["25FE-FE0F"],
+ "unicode_alternates": [
+ "25FE-FE0F"
+ ],
"name": "black medium small square",
"shortname": ":black_medium_small_square:",
"category": "other",
@@ -1085,29 +2353,40 @@
},
"black_medium_square": {
"unicode": "25FC",
- "unicode_alternates": ["25FC-FE0F"],
+ "unicode_alternates": [
+ "25FC-FE0F"
+ ],
"name": "black medium square",
"shortname": ":black_medium_square:",
"category": "other",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["shape"],
+ "keywords": [
+ "shape"
+ ],
"moji": "◼"
},
"black_nib": {
"unicode": "2712",
- "unicode_alternates": ["2712-FE0F"],
+ "unicode_alternates": [
+ "2712-FE0F"
+ ],
"name": "black nib",
"shortname": ":black_nib:",
"category": "objects",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["pen", "stationery"],
+ "keywords": [
+ "pen",
+ "stationery"
+ ],
"moji": "✒"
},
"black_small_square": {
"unicode": "25AA",
- "unicode_alternates": ["25AA-FE0F"],
+ "unicode_alternates": [
+ "25AA-FE0F"
+ ],
"name": "black small square",
"shortname": ":black_small_square:",
"category": "other",
@@ -1124,7 +2403,9 @@
"category": "other",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["frame"],
+ "keywords": [
+ "frame"
+ ],
"moji": "🔲"
},
"blossom": {
@@ -1135,7 +2416,14 @@
"category": "nature",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["flowers", "nature", "yellow", "blossom", "daisy", "flower"],
+ "keywords": [
+ "flowers",
+ "nature",
+ "yellow",
+ "blossom",
+ "daisy",
+ "flower"
+ ],
"moji": "🌼"
},
"blowfish": {
@@ -1146,7 +2434,19 @@
"category": "nature",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["food", "nature", "ocean", "sea", "blowfish", "pufferfish", "puffer", "ballonfish", "toadfish", "fugu fish", "sushi"],
+ "keywords": [
+ "food",
+ "nature",
+ "ocean",
+ "sea",
+ "blowfish",
+ "pufferfish",
+ "puffer",
+ "ballonfish",
+ "toadfish",
+ "fugu fish",
+ "sushi"
+ ],
"moji": "🐡"
},
"blue_book": {
@@ -1157,7 +2457,11 @@
"category": "objects",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["knowledge", "library", "read"],
+ "keywords": [
+ "knowledge",
+ "library",
+ "read"
+ ],
"moji": "📘"
},
"blue_car": {
@@ -1168,7 +2472,13 @@
"category": "places",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["car", "suv", "car", "wagon", "automobile"],
+ "keywords": [
+ "car",
+ "suv",
+ "car",
+ "wagon",
+ "automobile"
+ ],
"moji": "🚙"
},
"blue_heart": {
@@ -1179,7 +2489,19 @@
"category": "emoticons",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["affection", "like", "love", "valentines", "blue", "heart", "love", "stability", "truth", "loyalty", "trust"],
+ "keywords": [
+ "affection",
+ "like",
+ "love",
+ "valentines",
+ "blue",
+ "heart",
+ "love",
+ "stability",
+ "truth",
+ "loyalty",
+ "trust"
+ ],
"moji": "💙"
},
"blush": {
@@ -1190,7 +2512,18 @@
"category": "emoticons",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["crush", "embarrassed", "face", "flushed", "happy", "shy", "smile", "smiling", "smile", "smiley"],
+ "keywords": [
+ "crush",
+ "embarrassed",
+ "face",
+ "flushed",
+ "happy",
+ "shy",
+ "smile",
+ "smiling",
+ "smile",
+ "smiley"
+ ],
"moji": "😊"
},
"boar": {
@@ -1201,7 +2534,10 @@
"category": "nature",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["animal", "nature"],
+ "keywords": [
+ "animal",
+ "nature"
+ ],
"moji": "🐗"
},
"bomb": {
@@ -1212,7 +2548,10 @@
"category": "objects",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["boom", "explode"],
+ "keywords": [
+ "boom",
+ "explode"
+ ],
"moji": "💣"
},
"book": {
@@ -1223,7 +2562,10 @@
"category": "objects",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["library", "literature"],
+ "keywords": [
+ "library",
+ "literature"
+ ],
"moji": "📖"
},
"book2": {
@@ -1234,7 +2576,13 @@
"category": "objects_symbols",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["library", "literature", "novel", "reading", "story"]
+ "keywords": [
+ "library",
+ "literature",
+ "novel",
+ "reading",
+ "story"
+ ]
},
"bookmark": {
"unicode": "1F516",
@@ -1244,7 +2592,9 @@
"category": "objects",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["favorite"],
+ "keywords": [
+ "favorite"
+ ],
"moji": "🔖"
},
"bookmark_tabs": {
@@ -1255,7 +2605,9 @@
"category": "objects",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["favorite"],
+ "keywords": [
+ "favorite"
+ ],
"moji": "📑"
},
"books": {
@@ -1266,7 +2618,10 @@
"category": "objects",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["library", "literature"],
+ "keywords": [
+ "library",
+ "literature"
+ ],
"moji": "📚"
},
"boom": {
@@ -1277,7 +2632,18 @@
"category": "emoticons",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["bomb", "explode", "explosion", "boom", "bang", "collision", "fire", "emphasis", "wow", "bam"],
+ "keywords": [
+ "bomb",
+ "explode",
+ "explosion",
+ "boom",
+ "bang",
+ "collision",
+ "fire",
+ "emphasis",
+ "wow",
+ "bam"
+ ],
"moji": "💥"
},
"boot": {
@@ -1288,7 +2654,10 @@
"category": "emoticons",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["fashion", "shoes"],
+ "keywords": [
+ "fashion",
+ "shoes"
+ ],
"moji": "👢"
},
"bouquet": {
@@ -1299,7 +2668,10 @@
"category": "nature",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["flowers", "nature"],
+ "keywords": [
+ "flowers",
+ "nature"
+ ],
"moji": "💐"
},
"bouquet2": {
@@ -1308,9 +2680,16 @@
"name": "bouquet of flowers",
"shortname": ":bouquet2:",
"category": "celebration",
- "aliases": [":bouquet_of_flowers:"],
+ "aliases": [
+ ":bouquet_of_flowers:"
+ ],
"aliases_ascii": [],
- "keywords": ["nature", "marriage", "wedding", "bride"]
+ "keywords": [
+ "nature",
+ "marriage",
+ "wedding",
+ "bride"
+ ]
},
"bow": {
"unicode": "1F647",
@@ -1320,9 +2699,120 @@
"category": "emoticons",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["boy", "male", "man", "sorry", "bow", "respect", "curtsy", "bend"],
+ "keywords": [
+ "boy",
+ "male",
+ "man",
+ "sorry",
+ "bow",
+ "respect",
+ "curtsy",
+ "bend"
+ ],
"moji": "🙇"
},
+ "bow_and_arrow": {
+ "unicode": "1F3F9",
+ "unicode_alternates": "",
+ "name": "bow and arrow",
+ "shortname": ":bow_and_arrow:",
+ "category": "activity",
+ "aliases": [
+ ":archery:"
+ ],
+ "aliases_ascii": [],
+ "keywords": []
+ },
+ "bow_tone1": {
+ "unicode": "1F647-1F3FB",
+ "unicode_alternates": "",
+ "name": "person bowing deeply tone 1",
+ "shortname": ":bow_tone1:",
+ "category": "people",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": [
+ "boy",
+ "male",
+ "man",
+ "sorry",
+ "bow",
+ "respect",
+ "bend"
+ ]
+ },
+ "bow_tone2": {
+ "unicode": "1F647-1F3FC",
+ "unicode_alternates": "",
+ "name": "person bowing deeply tone 2",
+ "shortname": ":bow_tone2:",
+ "category": "people",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": [
+ "boy",
+ "male",
+ "man",
+ "sorry",
+ "bow",
+ "respect",
+ "bend"
+ ]
+ },
+ "bow_tone3": {
+ "unicode": "1F647-1F3FD",
+ "unicode_alternates": "",
+ "name": "person bowing deeply tone 3",
+ "shortname": ":bow_tone3:",
+ "category": "people",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": [
+ "boy",
+ "male",
+ "man",
+ "sorry",
+ "bow",
+ "respect",
+ "bend"
+ ]
+ },
+ "bow_tone4": {
+ "unicode": "1F647-1F3FE",
+ "unicode_alternates": "",
+ "name": "person bowing deeply tone 4",
+ "shortname": ":bow_tone4:",
+ "category": "people",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": [
+ "boy",
+ "male",
+ "man",
+ "sorry",
+ "bow",
+ "respect",
+ "bend"
+ ]
+ },
+ "bow_tone5": {
+ "unicode": "1F647-1F3FF",
+ "unicode_alternates": "",
+ "name": "person bowing deeply tone 5",
+ "shortname": ":bow_tone5:",
+ "category": "people",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": [
+ "boy",
+ "male",
+ "man",
+ "sorry",
+ "bow",
+ "respect",
+ "bend"
+ ]
+ },
"bowling": {
"unicode": "1F3B3",
"unicode_alternates": [],
@@ -1331,7 +2821,18 @@
"category": "objects",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["fun", "play", "sports", "bowl", "bowling", "ball", "pin", "strike", "spare", "game"],
+ "keywords": [
+ "fun",
+ "play",
+ "sports",
+ "bowl",
+ "bowling",
+ "ball",
+ "pin",
+ "strike",
+ "spare",
+ "game"
+ ],
"moji": "🎳"
},
"boy": {
@@ -1342,9 +2843,83 @@
"category": "emoticons",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["guy", "male", "man"],
+ "keywords": [
+ "guy",
+ "male",
+ "man"
+ ],
"moji": "👦"
},
+ "boy_tone1": {
+ "unicode": "1F466-1F3FB",
+ "unicode_alternates": "",
+ "name": "boy tone 1",
+ "shortname": ":boy_tone1:",
+ "category": "people",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": [
+ "male",
+ "kid",
+ "child"
+ ]
+ },
+ "boy_tone2": {
+ "unicode": "1F466-1F3FC",
+ "unicode_alternates": "",
+ "name": "boy tone 2",
+ "shortname": ":boy_tone2:",
+ "category": "people",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": [
+ "male",
+ "kid",
+ "child"
+ ]
+ },
+ "boy_tone3": {
+ "unicode": "1F466-1F3FD",
+ "unicode_alternates": "",
+ "name": "boy tone 3",
+ "shortname": ":boy_tone3:",
+ "category": "people",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": [
+ "male",
+ "kid",
+ "child"
+ ]
+ },
+ "boy_tone4": {
+ "unicode": "1F466-1F3FE",
+ "unicode_alternates": "",
+ "name": "boy tone 4",
+ "shortname": ":boy_tone4:",
+ "category": "people",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": [
+ "male",
+ "kid",
+ "child"
+ ]
+ },
+ "boy_tone5": {
+ "unicode": "1F466-1F3FF",
+ "unicode_alternates": "",
+ "name": "boy tone 5",
+ "shortname": ":boy_tone5:",
+ "category": "people",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": [
+ "male",
+ "kid",
+ "child"
+ ]
+ },
"boys_symbol": {
"unicode": "1F6C9",
"unicode_alternates": [],
@@ -1353,7 +2928,10 @@
"category": "objects_symbols",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["male", "child"]
+ "keywords": [
+ "male",
+ "child"
+ ]
},
"bread": {
"unicode": "1F35E",
@@ -1363,7 +2941,15 @@
"category": "objects",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["breakfast", "food", "toast", "wheat", "bread", "loaf", "yeast"],
+ "keywords": [
+ "breakfast",
+ "food",
+ "toast",
+ "wheat",
+ "bread",
+ "loaf",
+ "yeast"
+ ],
"moji": "🍞"
},
"bride_with_veil": {
@@ -1374,9 +2960,121 @@
"category": "emoticons",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["couple", "marriage", "wedding", "bride", "wedding", "planning", "veil", "gown", "dress", "engagement", "white"],
+ "keywords": [
+ "couple",
+ "marriage",
+ "wedding",
+ "bride",
+ "wedding",
+ "planning",
+ "veil",
+ "gown",
+ "dress",
+ "engagement",
+ "white"
+ ],
"moji": "👰"
},
+ "bride_with_veil_tone1": {
+ "unicode": "1F470-1F3FB",
+ "unicode_alternates": "",
+ "name": "bride with veil tone 1",
+ "shortname": ":bride_with_veil_tone1:",
+ "category": "people",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": [
+ "couple",
+ "marriage",
+ "wedding",
+ "wedding",
+ "planning",
+ "gown",
+ "dress",
+ "engagement",
+ "white"
+ ]
+ },
+ "bride_with_veil_tone2": {
+ "unicode": "1F470-1F3FC",
+ "unicode_alternates": "",
+ "name": "bride with veil tone 2",
+ "shortname": ":bride_with_veil_tone2:",
+ "category": "people",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": [
+ "couple",
+ "marriage",
+ "wedding",
+ "wedding",
+ "planning",
+ "gown",
+ "dress",
+ "engagement",
+ "white"
+ ]
+ },
+ "bride_with_veil_tone3": {
+ "unicode": "1F470-1F3FD",
+ "unicode_alternates": "",
+ "name": "bride with veil tone 3",
+ "shortname": ":bride_with_veil_tone3:",
+ "category": "people",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": [
+ "couple",
+ "marriage",
+ "wedding",
+ "wedding",
+ "planning",
+ "gown",
+ "dress",
+ "engagement",
+ "white"
+ ]
+ },
+ "bride_with_veil_tone4": {
+ "unicode": "1F470-1F3FE",
+ "unicode_alternates": "",
+ "name": "bride with veil tone 4",
+ "shortname": ":bride_with_veil_tone4:",
+ "category": "people",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": [
+ "couple",
+ "marriage",
+ "wedding",
+ "wedding",
+ "planning",
+ "gown",
+ "dress",
+ "engagement",
+ "white"
+ ]
+ },
+ "bride_with_veil_tone5": {
+ "unicode": "1F470-1F3FF",
+ "unicode_alternates": "",
+ "name": "bride with veil tone 5",
+ "shortname": ":bride_with_veil_tone5:",
+ "category": "people",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": [
+ "couple",
+ "marriage",
+ "wedding",
+ "wedding",
+ "planning",
+ "gown",
+ "dress",
+ "engagement",
+ "white"
+ ]
+ },
"bridge_at_night": {
"unicode": "1F309",
"unicode_alternates": [],
@@ -1385,7 +3083,18 @@
"category": "places",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["photo", "sanfrancisco", "bridge", "night", "water", "road", "evening", "suspension", "golden", "gate"],
+ "keywords": [
+ "photo",
+ "sanfrancisco",
+ "bridge",
+ "night",
+ "water",
+ "road",
+ "evening",
+ "suspension",
+ "golden",
+ "gate"
+ ],
"moji": "🌉"
},
"briefcase": {
@@ -1396,7 +3105,11 @@
"category": "emoticons",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["business", "documents", "work"],
+ "keywords": [
+ "business",
+ "documents",
+ "work"
+ ],
"moji": "💼"
},
"broken_heart": {
@@ -1406,8 +3119,13 @@
"shortname": ":broken_heart:",
"category": "emoticons",
"aliases": [],
- "aliases_ascii": ["</3"],
- "keywords": ["sad", "sorry"],
+ "aliases_ascii": [
+ "</3"
+ ],
+ "keywords": [
+ "sad",
+ "sorry"
+ ],
"moji": "💔"
},
"bug": {
@@ -1418,7 +3136,14 @@
"category": "nature",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["insect", "nature", "bug", "insect", "virus", "error"],
+ "keywords": [
+ "insect",
+ "nature",
+ "bug",
+ "insect",
+ "virus",
+ "error"
+ ],
"moji": "🐛"
},
"bulb": {
@@ -1429,7 +3154,13 @@
"category": "objects",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["electricity", "light", "idea", "bulb", "light"],
+ "keywords": [
+ "electricity",
+ "light",
+ "idea",
+ "bulb",
+ "light"
+ ],
"moji": "💡"
},
"bullettrain_front": {
@@ -1440,7 +3171,12 @@
"category": "places",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["transportation", "train", "bullet", "rail"],
+ "keywords": [
+ "transportation",
+ "train",
+ "bullet",
+ "rail"
+ ],
"moji": "🚅"
},
"bullettrain_side": {
@@ -1451,7 +3187,13 @@
"category": "places",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["transportation", "vehicle", "train", "bullet", "rail"],
+ "keywords": [
+ "transportation",
+ "vehicle",
+ "train",
+ "bullet",
+ "rail"
+ ],
"moji": "🚄"
},
"bullhorn": {
@@ -1462,7 +3204,12 @@
"category": "objects_symbols",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["sound", "noise", "announcement", "megaphone"]
+ "keywords": [
+ "sound",
+ "noise",
+ "announcement",
+ "megaphone"
+ ]
},
"bullhorn_waves": {
"unicode": "1F56C",
@@ -1470,9 +3217,26 @@
"name": "bullhorn with sound waves",
"shortname": ":bullhorn_waves:",
"category": "objects_symbols",
- "aliases": [":bullhorn_with_sound_waves:"],
+ "aliases": [
+ ":bullhorn_with_sound_waves:"
+ ],
"aliases_ascii": [],
- "keywords": ["sound", "noise", "announcement", "megaphone"]
+ "keywords": [
+ "sound",
+ "noise",
+ "announcement",
+ "megaphone"
+ ]
+ },
+ "burrito": {
+ "unicode": "1F32F",
+ "unicode_alternates": "",
+ "name": "burrito",
+ "shortname": ":burrito:",
+ "category": "foods",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": []
},
"bus": {
"unicode": "1F68C",
@@ -1482,7 +3246,16 @@
"category": "places",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["car", "transportation", "vehicle", "bus", "school", "city", "transportation", "public"],
+ "keywords": [
+ "car",
+ "transportation",
+ "vehicle",
+ "bus",
+ "school",
+ "city",
+ "transportation",
+ "public"
+ ],
"moji": "🚌"
},
"busstop": {
@@ -1493,7 +3266,14 @@
"category": "places",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["transportation", "bus", "stop", "city", "transport", "transportation"],
+ "keywords": [
+ "transportation",
+ "bus",
+ "stop",
+ "city",
+ "transport",
+ "transportation"
+ ],
"moji": "🚏"
},
"bust_in_silhouette": {
@@ -1504,7 +3284,24 @@
"category": "emoticons",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["human", "man", "person", "user", "silhouette", "person", "user", "member", "account", "guest", "icon", "avatar", "profile", "me", "myself", "i"],
+ "keywords": [
+ "human",
+ "man",
+ "person",
+ "user",
+ "silhouette",
+ "person",
+ "user",
+ "member",
+ "account",
+ "guest",
+ "icon",
+ "avatar",
+ "profile",
+ "me",
+ "myself",
+ "i"
+ ],
"moji": "👤"
},
"busts_in_silhouette": {
@@ -1515,7 +3312,22 @@
"category": "emoticons",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["group", "human", "man", "person", "team", "user", "silhouette", "silhouettes", "people", "user", "members", "accounts", "relationship", "shadow"],
+ "keywords": [
+ "group",
+ "human",
+ "man",
+ "person",
+ "team",
+ "user",
+ "silhouette",
+ "silhouettes",
+ "people",
+ "user",
+ "members",
+ "accounts",
+ "relationship",
+ "shadow"
+ ],
"moji": "👥"
},
"cactus": {
@@ -1526,7 +3338,16 @@
"category": "nature",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["nature", "plant", "vegetable", "cactus", "desert", "drought", "spike", "poke"],
+ "keywords": [
+ "nature",
+ "plant",
+ "vegetable",
+ "cactus",
+ "desert",
+ "drought",
+ "spike",
+ "poke"
+ ],
"moji": "🌵"
},
"cake": {
@@ -1537,7 +3358,14 @@
"category": "objects",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["desert", "food", "cake", "short", "dessert", "strawberry"],
+ "keywords": [
+ "desert",
+ "food",
+ "cake",
+ "short",
+ "dessert",
+ "strawberry"
+ ],
"moji": "🍰"
},
"calculator": {
@@ -1546,9 +3374,17 @@
"name": "pocket calculator",
"shortname": ":calculator:",
"category": "objects_symbols",
- "aliases": [":pocket calculator:"],
- "aliases_ascii": [],
- "keywords": ["add", "subtract", "multiple", "divide", "scientific"]
+ "aliases": [
+ ":pocket calculator:"
+ ],
+ "aliases_ascii": [],
+ "keywords": [
+ "add",
+ "subtract",
+ "multiple",
+ "divide",
+ "scientific"
+ ]
},
"calendar": {
"unicode": "1F4C6",
@@ -1558,7 +3394,9 @@
"category": "objects",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["schedule"],
+ "keywords": [
+ "schedule"
+ ],
"moji": "📆"
},
"calendar_spiral": {
@@ -1567,9 +3405,15 @@
"name": "spiral calendar pad",
"shortname": ":calendar_spiral:",
"category": "objects_symbols",
- "aliases": [":spiral_calendar_pad:"],
+ "aliases": [
+ ":spiral_calendar_pad:"
+ ],
"aliases_ascii": [],
- "keywords": ["schedule", "date", "day"]
+ "keywords": [
+ "schedule",
+ "date",
+ "day"
+ ]
},
"calling": {
"unicode": "1F4F2",
@@ -1579,7 +3423,10 @@
"category": "objects",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["incoming", "iphone"],
+ "keywords": [
+ "incoming",
+ "iphone"
+ ],
"moji": "📲"
},
"camel": {
@@ -1590,7 +3437,22 @@
"category": "nature",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["animal", "hot", "nature", "bactrian", "camel", "hump", "desert", "central asia", "heat", "hot", "water", "hump day", "wednesday", "sex"],
+ "keywords": [
+ "animal",
+ "hot",
+ "nature",
+ "bactrian",
+ "camel",
+ "hump",
+ "desert",
+ "central asia",
+ "heat",
+ "hot",
+ "water",
+ "hump day",
+ "wednesday",
+ "sex"
+ ],
"moji": "🐫"
},
"camera": {
@@ -1601,7 +3463,10 @@
"category": "objects",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["gadgets", "photo"],
+ "keywords": [
+ "gadgets",
+ "photo"
+ ],
"moji": "📷"
},
"camera_with_flash": {
@@ -1612,7 +3477,10 @@
"category": "objects_symbols",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["photo", "picture"]
+ "keywords": [
+ "photo",
+ "picture"
+ ]
},
"camping": {
"unicode": "1F3D5",
@@ -1622,7 +3490,13 @@
"category": "travel_places",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["outdoors", "nature", "wilderness", "roughing", "activity"]
+ "keywords": [
+ "outdoors",
+ "nature",
+ "wilderness",
+ "roughing",
+ "activity"
+ ]
},
"cancellation_x": {
"unicode": "1F5D9",
@@ -1632,17 +3506,35 @@
"category": "objects_symbols",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["cancel", "stop", "delete"]
+ "keywords": [
+ "cancel",
+ "stop",
+ "delete"
+ ]
},
"cancer": {
"unicode": "264B",
- "unicode_alternates": ["264B-FE0F"],
+ "unicode_alternates": [
+ "264B-FE0F"
+ ],
"name": "cancer",
"shortname": ":cancer:",
"category": "other",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["cancer", "crab", "astrology", "greek", "constellation", "stars", "zodiac", "sign", "sign", "zodiac", "horoscope"],
+ "keywords": [
+ "cancer",
+ "crab",
+ "astrology",
+ "greek",
+ "constellation",
+ "stars",
+ "zodiac",
+ "sign",
+ "sign",
+ "zodiac",
+ "horoscope"
+ ],
"moji": "♋"
},
"candle": {
@@ -1653,7 +3545,10 @@
"category": "objects_symbols",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["light", "wax"]
+ "keywords": [
+ "light",
+ "wax"
+ ]
},
"candy": {
"unicode": "1F36C",
@@ -1663,7 +3558,14 @@
"category": "objects",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["desert", "snack", "candy", "sugar", "sweet", "hard"],
+ "keywords": [
+ "desert",
+ "snack",
+ "candy",
+ "sugar",
+ "sweet",
+ "hard"
+ ],
"moji": "🍬"
},
"capital_abcd": {
@@ -1674,18 +3576,37 @@
"category": "other",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["alphabet", "blue-square", "words"],
+ "keywords": [
+ "alphabet",
+ "blue-square",
+ "words"
+ ],
"moji": "🔠"
},
"capricorn": {
"unicode": "2651",
- "unicode_alternates": ["2651-FE0F"],
+ "unicode_alternates": [
+ "2651-FE0F"
+ ],
"name": "capricorn",
"shortname": ":capricorn:",
"category": "other",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["capricorn", "sea-goat", "goat-horned", "astrology", "greek", "constellation", "stars", "zodiac", "sign", "sign", "zodiac", "horoscope"],
+ "keywords": [
+ "capricorn",
+ "sea-goat",
+ "goat-horned",
+ "astrology",
+ "greek",
+ "constellation",
+ "stars",
+ "zodiac",
+ "sign",
+ "sign",
+ "zodiac",
+ "horoscope"
+ ],
"moji": "♑"
},
"card_box": {
@@ -1694,9 +3615,14 @@
"name": "card file box",
"shortname": ":card_box:",
"category": "objects_symbols",
- "aliases": [":card_file_box:"],
+ "aliases": [
+ ":card_file_box:"
+ ],
"aliases_ascii": [],
- "keywords": ["index", "organization"]
+ "keywords": [
+ "index",
+ "organization"
+ ]
},
"card_index": {
"unicode": "1F4C7",
@@ -1706,7 +3632,10 @@
"category": "objects",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["business", "stationery"],
+ "keywords": [
+ "business",
+ "stationery"
+ ],
"moji": "📇"
},
"carousel_horse": {
@@ -1717,7 +3646,19 @@
"category": "places",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["carnival", "horse", "photo", "carousel", "horse", "amusement", "park", "ride", "entertainment", "park", "fair"],
+ "keywords": [
+ "carnival",
+ "horse",
+ "photo",
+ "carousel",
+ "horse",
+ "amusement",
+ "park",
+ "ride",
+ "entertainment",
+ "park",
+ "fair"
+ ],
"moji": "🎠"
},
"cartridge": {
@@ -1726,9 +3667,21 @@
"name": "tape cartridge",
"shortname": ":cartridge:",
"category": "objects_symbols",
- "aliases": [":tape_cartridge:"],
- "aliases_ascii": [],
- "keywords": ["oldschool", "save", "technology", "disk", "storage", "information", "computer", "drive", "megabyte"]
+ "aliases": [
+ ":tape_cartridge:"
+ ],
+ "aliases_ascii": [],
+ "keywords": [
+ "oldschool",
+ "save",
+ "technology",
+ "disk",
+ "storage",
+ "information",
+ "computer",
+ "drive",
+ "megabyte"
+ ]
},
"cat": {
"unicode": "1F431",
@@ -1738,7 +3691,10 @@
"category": "nature",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["animal", "meow"],
+ "keywords": [
+ "animal",
+ "meow"
+ ],
"moji": "🐱"
},
"cat2": {
@@ -1749,9 +3705,36 @@
"category": "nature",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["animal", "meow", "pet", "cat", "kitten", "meow"],
+ "keywords": [
+ "animal",
+ "meow",
+ "pet",
+ "cat",
+ "kitten",
+ "meow"
+ ],
"moji": "🐈"
},
+ "cd": {
+ "unicode": "1F4BF",
+ "unicode_alternates": "",
+ "name": "optical disc",
+ "shortname": ":cd:",
+ "category": "objects",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": [
+ "disc",
+ "disk",
+ "dvd",
+ "technology",
+ "blu-ray",
+ "cd",
+ "computer",
+ "object",
+ "office"
+ ]
+ },
"celtic_cross": {
"unicode": "1F548",
"unicode_alternates": [],
@@ -1760,7 +3743,35 @@
"category": "objects_symbols",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["religion", "symbol"]
+ "keywords": [
+ "religion",
+ "symbol"
+ ]
+ },
+ "chains": {
+ "unicode": "26D3",
+ "unicode_alternates": "",
+ "name": "chains",
+ "shortname": ":chains:",
+ "category": "objects",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": [
+ "chain",
+ "object"
+ ]
+ },
+ "champagne": {
+ "unicode": "1F37E",
+ "unicode_alternates": "",
+ "name": "bottle with popping cork",
+ "shortname": ":champagne:",
+ "category": "foods",
+ "aliases": [
+ ":bottle_with_popping_cork:"
+ ],
+ "aliases_ascii": [],
+ "keywords": []
},
"chart": {
"unicode": "1F4B9",
@@ -1770,7 +3781,10 @@
"category": "other",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["graph", "green-square"],
+ "keywords": [
+ "graph",
+ "green-square"
+ ],
"moji": "💹"
},
"chart_with_downwards_trend": {
@@ -1781,7 +3795,9 @@
"category": "objects",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["graph"],
+ "keywords": [
+ "graph"
+ ],
"moji": "📉"
},
"chart_with_upwards_trend": {
@@ -1792,7 +3808,9 @@
"category": "objects",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["graph"],
+ "keywords": [
+ "graph"
+ ],
"moji": "📈"
},
"checkered_flag": {
@@ -1803,9 +3821,33 @@
"category": "objects",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["contest", "finishline", "gokart", "rase", "checkered", "chequred", "race", "flag", "finish", "complete", "end"],
+ "keywords": [
+ "contest",
+ "finishline",
+ "gokart",
+ "rase",
+ "checkered",
+ "chequred",
+ "race",
+ "flag",
+ "finish",
+ "complete",
+ "end"
+ ],
"moji": "🏁"
},
+ "cheese": {
+ "unicode": "1F9C0",
+ "unicode_alternates": "",
+ "name": "cheese wedge",
+ "shortname": ":cheese:",
+ "category": "foods",
+ "aliases": [
+ ":cheese_wedge:"
+ ],
+ "aliases_ascii": [],
+ "keywords": []
+ },
"cherries": {
"unicode": "1F352",
"unicode_alternates": [],
@@ -1814,7 +3856,15 @@
"category": "objects",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["food", "fruit", "cherry", "cherries", "tree", "fruit", "pit"],
+ "keywords": [
+ "food",
+ "fruit",
+ "cherry",
+ "cherries",
+ "tree",
+ "fruit",
+ "pit"
+ ],
"moji": "🍒"
},
"cherry_blossom": {
@@ -1825,7 +3875,15 @@
"category": "nature",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["flower", "nature", "plant", "cherry", "blossom", "tree", "flower"],
+ "keywords": [
+ "flower",
+ "nature",
+ "plant",
+ "cherry",
+ "blossom",
+ "tree",
+ "flower"
+ ],
"moji": "🌸"
},
"chestnut": {
@@ -1836,7 +3894,14 @@
"category": "nature",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["food", "squirrel", "chestnut", "roasted", "food", "tree"],
+ "keywords": [
+ "food",
+ "squirrel",
+ "chestnut",
+ "roasted",
+ "food",
+ "tree"
+ ],
"moji": "🌰"
},
"chicken": {
@@ -1847,7 +3912,14 @@
"category": "nature",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["animal", "cluck", "chicken", "hen", "poultry", "livestock"],
+ "keywords": [
+ "animal",
+ "cluck",
+ "chicken",
+ "hen",
+ "poultry",
+ "livestock"
+ ],
"moji": "🐔"
},
"children_crossing": {
@@ -1858,7 +3930,16 @@
"category": "other",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["school", "children", "kids", "caution", "crossing", "street", "crosswalk", "slow"],
+ "keywords": [
+ "school",
+ "children",
+ "kids",
+ "caution",
+ "crossing",
+ "street",
+ "crosswalk",
+ "slow"
+ ],
"moji": "🚸"
},
"chipmunk": {
@@ -1869,7 +3950,10 @@
"category": "nature",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["animal", "nature"]
+ "keywords": [
+ "animal",
+ "nature"
+ ]
},
"chocolate_bar": {
"unicode": "1F36B",
@@ -1879,7 +3963,16 @@
"category": "objects",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["desert", "food", "snack", "chocolate", "bar", "candy", "coca", "hershey&#039;s"],
+ "keywords": [
+ "desert",
+ "food",
+ "snack",
+ "chocolate",
+ "bar",
+ "candy",
+ "coca",
+ "hershey&#039;s"
+ ],
"moji": "🍫"
},
"christmas_tree": {
@@ -1890,18 +3983,42 @@
"category": "objects",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["celebration", "december", "festival", "vacation", "xmas", "christmas", "xmas", "santa", "holiday", "winter", "december", "santa", "evergreen", "ornaments", "jesus", "gifts", "presents"],
+ "keywords": [
+ "celebration",
+ "december",
+ "festival",
+ "vacation",
+ "xmas",
+ "christmas",
+ "xmas",
+ "santa",
+ "holiday",
+ "winter",
+ "december",
+ "santa",
+ "evergreen",
+ "ornaments",
+ "jesus",
+ "gifts",
+ "presents"
+ ],
"moji": "🎄"
},
"church": {
"unicode": "26EA",
- "unicode_alternates": ["26EA-FE0F"],
+ "unicode_alternates": [
+ "26EA-FE0F"
+ ],
"name": "church",
"shortname": ":church:",
"category": "places",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["building", "christ", "religion"],
+ "keywords": [
+ "building",
+ "christ",
+ "religion"
+ ],
"moji": "⛪"
},
"cinema": {
@@ -1912,7 +4029,17 @@
"category": "other",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["blue-square", "film", "movie", "record", "cinema", "movie", "theater", "motion", "picture"],
+ "keywords": [
+ "blue-square",
+ "film",
+ "movie",
+ "record",
+ "cinema",
+ "movie",
+ "theater",
+ "motion",
+ "picture"
+ ],
"moji": "🎦"
},
"circus_tent": {
@@ -1923,7 +4050,18 @@
"category": "places",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["carnival", "festival", "party", "circus", "tent", "event", "carnival", "big", "top", "canvas"],
+ "keywords": [
+ "carnival",
+ "festival",
+ "party",
+ "circus",
+ "tent",
+ "event",
+ "carnival",
+ "big",
+ "top",
+ "canvas"
+ ],
"moji": "🎪"
},
"city_dusk": {
@@ -1934,7 +4072,18 @@
"category": "places",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["photo", "city", "scape", "sunset", "dusk", "lights", "evening", "metropolitan", "night", "dark"],
+ "keywords": [
+ "photo",
+ "city",
+ "scape",
+ "sunset",
+ "dusk",
+ "lights",
+ "evening",
+ "metropolitan",
+ "night",
+ "dark"
+ ],
"moji": "🌆"
},
"city_sunset": {
@@ -1943,9 +4092,22 @@
"name": "sunset over buildings",
"shortname": ":city_sunset:",
"category": "places",
- "aliases": [":city_sunrise:"],
- "aliases_ascii": [],
- "keywords": ["photo", "city", "scape", "sunrise", "dawn", "light", "morning", "metropolitan", "rise", "sun"],
+ "aliases": [
+ ":city_sunrise:"
+ ],
+ "aliases_ascii": [],
+ "keywords": [
+ "photo",
+ "city",
+ "scape",
+ "sunrise",
+ "dawn",
+ "light",
+ "morning",
+ "metropolitan",
+ "rise",
+ "sun"
+ ],
"moji": "🌇"
},
"cityscape": {
@@ -1956,7 +4118,32 @@
"category": "travel_places",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["skyscraper", "city", "view", "lights", "buiildings", "metropolis"]
+ "keywords": [
+ "skyscraper",
+ "city",
+ "view",
+ "lights",
+ "buiildings",
+ "metropolis"
+ ]
+ },
+ "cl": {
+ "unicode": "1F191",
+ "unicode_alternates": "",
+ "name": "squared cl",
+ "shortname": ":cl:",
+ "category": "symbols",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": [
+ "alphabet",
+ "red-square",
+ "words",
+ "cl",
+ "clear",
+ "symbol",
+ "word"
+ ]
},
"clap": {
"unicode": "1F44F",
@@ -1966,9 +4153,120 @@
"category": "emoticons",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["applause", "congrats", "hands", "praise", "clapping", "appreciation", "approval", "sound", "encouragement", "enthusiasm"],
+ "keywords": [
+ "applause",
+ "congrats",
+ "hands",
+ "praise",
+ "clapping",
+ "appreciation",
+ "approval",
+ "sound",
+ "encouragement",
+ "enthusiasm"
+ ],
"moji": "👏"
},
+ "clap_tone1": {
+ "unicode": "1F44F-1F3FB",
+ "unicode_alternates": "",
+ "name": "clapping hands sign tone 1",
+ "shortname": ":clap_tone1:",
+ "category": "people",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": [
+ "applause",
+ "congrats",
+ "praise",
+ "clap",
+ "appreciation",
+ "approval",
+ "sound",
+ "encouragement",
+ "enthusiasm"
+ ]
+ },
+ "clap_tone2": {
+ "unicode": "1F44F-1F3FC",
+ "unicode_alternates": "",
+ "name": "clapping hands sign tone 2",
+ "shortname": ":clap_tone2:",
+ "category": "people",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": [
+ "applause",
+ "congrats",
+ "praise",
+ "clap",
+ "appreciation",
+ "approval",
+ "sound",
+ "encouragement",
+ "enthusiasm"
+ ]
+ },
+ "clap_tone3": {
+ "unicode": "1F44F-1F3FD",
+ "unicode_alternates": "",
+ "name": "clapping hands sign tone 3",
+ "shortname": ":clap_tone3:",
+ "category": "people",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": [
+ "applause",
+ "congrats",
+ "praise",
+ "clap",
+ "appreciation",
+ "approval",
+ "sound",
+ "encouragement",
+ "enthusiasm"
+ ]
+ },
+ "clap_tone4": {
+ "unicode": "1F44F-1F3FE",
+ "unicode_alternates": "",
+ "name": "clapping hands sign tone 4",
+ "shortname": ":clap_tone4:",
+ "category": "people",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": [
+ "applause",
+ "congrats",
+ "praise",
+ "clap",
+ "appreciation",
+ "approval",
+ "sound",
+ "encouragement",
+ "enthusiasm"
+ ]
+ },
+ "clap_tone5": {
+ "unicode": "1F44F-1F3FF",
+ "unicode_alternates": "",
+ "name": "clapping hands sign tone 5",
+ "shortname": ":clap_tone5:",
+ "category": "people",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": [
+ "applause",
+ "congrats",
+ "praise",
+ "clap",
+ "appreciation",
+ "approval",
+ "sound",
+ "encouragement",
+ "enthusiasm"
+ ]
+ },
"clapper": {
"unicode": "1F3AC",
"unicode_alternates": [],
@@ -1977,7 +4275,17 @@
"category": "objects",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["film", "movie", "record", "clapper", "board", "clapboard", "movie", "film", "take"],
+ "keywords": [
+ "film",
+ "movie",
+ "record",
+ "clapper",
+ "board",
+ "clapboard",
+ "movie",
+ "film",
+ "take"
+ ],
"moji": "🎬"
},
"classical_building": {
@@ -1988,7 +4296,13 @@
"category": "travel_places",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["government", "architecture", "history", "iconic", "genre"]
+ "keywords": [
+ "government",
+ "architecture",
+ "history",
+ "iconic",
+ "genre"
+ ]
},
"clipboard": {
"unicode": "1F4CB",
@@ -1998,7 +4312,10 @@
"category": "objects",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["documents", "stationery"],
+ "keywords": [
+ "documents",
+ "stationery"
+ ],
"moji": "📋"
},
"clock": {
@@ -2007,9 +4324,13 @@
"name": "mantlepiece clock",
"shortname": ":clock:",
"category": "objects_symbols",
- "aliases": [":mantlepiece_clock:"],
+ "aliases": [
+ ":mantlepiece_clock:"
+ ],
"aliases_ascii": [],
- "keywords": ["time"]
+ "keywords": [
+ "time"
+ ]
},
"clock1": {
"unicode": "1F550",
@@ -2019,7 +4340,10 @@
"category": "other",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["clock", "time"],
+ "keywords": [
+ "clock",
+ "time"
+ ],
"moji": "🕐"
},
"clock10": {
@@ -2030,7 +4354,10 @@
"category": "other",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["clock", "time"],
+ "keywords": [
+ "clock",
+ "time"
+ ],
"moji": "🕙"
},
"clock1030": {
@@ -2041,7 +4368,10 @@
"category": "other",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["clock", "time"],
+ "keywords": [
+ "clock",
+ "time"
+ ],
"moji": "🕥"
},
"clock11": {
@@ -2052,7 +4382,10 @@
"category": "other",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["clock", "time"],
+ "keywords": [
+ "clock",
+ "time"
+ ],
"moji": "🕚"
},
"clock1130": {
@@ -2063,7 +4396,10 @@
"category": "other",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["clock", "time"],
+ "keywords": [
+ "clock",
+ "time"
+ ],
"moji": "🕦"
},
"clock12": {
@@ -2074,7 +4410,10 @@
"category": "other",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["clock", "time"],
+ "keywords": [
+ "clock",
+ "time"
+ ],
"moji": "🕛"
},
"clock1230": {
@@ -2085,7 +4424,10 @@
"category": "other",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["clock", "time"]
+ "keywords": [
+ "clock",
+ "time"
+ ]
},
"clock130": {
"unicode": "1F55C",
@@ -2095,7 +4437,10 @@
"category": "other",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["clock", "time"],
+ "keywords": [
+ "clock",
+ "time"
+ ],
"moji": "🕜"
},
"clock2": {
@@ -2106,7 +4451,10 @@
"category": "other",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["clock", "time"],
+ "keywords": [
+ "clock",
+ "time"
+ ],
"moji": "🕑"
},
"clock230": {
@@ -2117,7 +4465,10 @@
"category": "other",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["clock", "time"],
+ "keywords": [
+ "clock",
+ "time"
+ ],
"moji": "🕝"
},
"clock3": {
@@ -2128,7 +4479,10 @@
"category": "other",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["clock", "time"],
+ "keywords": [
+ "clock",
+ "time"
+ ],
"moji": "🕒"
},
"clock330": {
@@ -2139,7 +4493,10 @@
"category": "other",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["clock", "time"],
+ "keywords": [
+ "clock",
+ "time"
+ ],
"moji": "🕞"
},
"clock4": {
@@ -2150,7 +4507,10 @@
"category": "other",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["clock", "time"],
+ "keywords": [
+ "clock",
+ "time"
+ ],
"moji": "🕓"
},
"clock430": {
@@ -2161,7 +4521,10 @@
"category": "other",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["clock", "time"],
+ "keywords": [
+ "clock",
+ "time"
+ ],
"moji": "🕟"
},
"clock5": {
@@ -2172,7 +4535,10 @@
"category": "other",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["clock", "time"],
+ "keywords": [
+ "clock",
+ "time"
+ ],
"moji": "🕔"
},
"clock530": {
@@ -2183,7 +4549,10 @@
"category": "other",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["clock", "time"],
+ "keywords": [
+ "clock",
+ "time"
+ ],
"moji": "🕠"
},
"clock6": {
@@ -2194,7 +4563,10 @@
"category": "other",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["clock", "time"],
+ "keywords": [
+ "clock",
+ "time"
+ ],
"moji": "🕕"
},
"clock630": {
@@ -2205,7 +4577,10 @@
"category": "other",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["clock", "time"],
+ "keywords": [
+ "clock",
+ "time"
+ ],
"moji": "🕡"
},
"clock7": {
@@ -2216,7 +4591,10 @@
"category": "other",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["clock", "time"],
+ "keywords": [
+ "clock",
+ "time"
+ ],
"moji": "🕖"
},
"clock730": {
@@ -2227,7 +4605,10 @@
"category": "other",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["clock", "time"],
+ "keywords": [
+ "clock",
+ "time"
+ ],
"moji": "🕢"
},
"clock8": {
@@ -2238,7 +4619,10 @@
"category": "other",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["clock", "time"],
+ "keywords": [
+ "clock",
+ "time"
+ ],
"moji": "🕗"
},
"clock830": {
@@ -2249,7 +4633,10 @@
"category": "other",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["clock", "time"],
+ "keywords": [
+ "clock",
+ "time"
+ ],
"moji": "🕣"
},
"clock9": {
@@ -2260,7 +4647,10 @@
"category": "other",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["clock", "time"],
+ "keywords": [
+ "clock",
+ "time"
+ ],
"moji": "🕘"
},
"clock930": {
@@ -2271,7 +4661,10 @@
"category": "other",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["clock", "time"],
+ "keywords": [
+ "clock",
+ "time"
+ ],
"moji": "🕤"
},
"clockwise_arrows": {
@@ -2280,9 +4673,13 @@
"name": "clockwise right and left semicircle arrows",
"shortname": ":clockwise_arrows:",
"category": "objects_symbols",
- "aliases": [":clockwise_right_and_left_semicircle_arrows:"],
+ "aliases": [
+ ":clockwise_right_and_left_semicircle_arrows:"
+ ],
"aliases_ascii": [],
- "keywords": ["sync"]
+ "keywords": [
+ "sync"
+ ]
},
"closed_book": {
"unicode": "1F4D5",
@@ -2292,7 +4689,11 @@
"category": "objects",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["knowledge", "library", "read"],
+ "keywords": [
+ "knowledge",
+ "library",
+ "read"
+ ],
"moji": "📕"
},
"closed_lock_with_key": {
@@ -2303,7 +4704,10 @@
"category": "objects",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["privacy", "security"],
+ "keywords": [
+ "privacy",
+ "security"
+ ],
"moji": "🔐"
},
"closed_umbrella": {
@@ -2314,18 +4718,35 @@
"category": "emoticons",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["drizzle", "rain", "weather", "umbrella", "closed", "rain", "moisture", "protection", "sun", "ultraviolet", "uv"],
+ "keywords": [
+ "drizzle",
+ "rain",
+ "weather",
+ "umbrella",
+ "closed",
+ "rain",
+ "moisture",
+ "protection",
+ "sun",
+ "ultraviolet",
+ "uv"
+ ],
"moji": "🌂"
},
"cloud": {
"unicode": "2601",
- "unicode_alternates": ["2601-FE0F"],
+ "unicode_alternates": [
+ "2601-FE0F"
+ ],
"name": "cloud",
"shortname": ":cloud:",
"category": "nature",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["sky", "weather"],
+ "keywords": [
+ "sky",
+ "weather"
+ ],
"moji": "☁"
},
"cloud_lightning": {
@@ -2334,9 +4755,14 @@
"name": "cloud with lightning",
"shortname": ":cloud_lightning:",
"category": "nature",
- "aliases": [":cloud_with_lightning:"],
+ "aliases": [
+ ":cloud_with_lightning:"
+ ],
"aliases_ascii": [],
- "keywords": ["weather", "thunder"]
+ "keywords": [
+ "weather",
+ "thunder"
+ ]
},
"cloud_rain": {
"unicode": "1F327",
@@ -2344,9 +4770,14 @@
"name": "cloud with rain",
"shortname": ":cloud_rain:",
"category": "nature",
- "aliases": [":cloud_with_rain:"],
+ "aliases": [
+ ":cloud_with_rain:"
+ ],
"aliases_ascii": [],
- "keywords": ["weather", "wet"]
+ "keywords": [
+ "weather",
+ "wet"
+ ]
},
"cloud_snow": {
"unicode": "1F328",
@@ -2354,9 +4785,14 @@
"name": "cloud with snow",
"shortname": ":cloud_snow:",
"category": "nature",
- "aliases": [":cloud_with_snow:"],
+ "aliases": [
+ ":cloud_with_snow:"
+ ],
"aliases_ascii": [],
- "keywords": ["weather", "cold"]
+ "keywords": [
+ "weather",
+ "cold"
+ ]
},
"cloud_tornado": {
"unicode": "1F32A",
@@ -2364,19 +4800,30 @@
"name": "cloud with tornado",
"shortname": ":cloud_tornado:",
"category": "nature",
- "aliases": [":cloud_with_tornado:"],
+ "aliases": [
+ ":cloud_with_tornado:"
+ ],
"aliases_ascii": [],
- "keywords": ["weather", "destruction", "funnel"]
+ "keywords": [
+ "weather",
+ "destruction",
+ "funnel"
+ ]
},
"clubs": {
"unicode": "2663",
- "unicode_alternates": ["2663-FE0F"],
+ "unicode_alternates": [
+ "2663-FE0F"
+ ],
"name": "black club suit",
"shortname": ":clubs:",
"category": "other",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["cards", "poker"],
+ "keywords": [
+ "cards",
+ "poker"
+ ],
"moji": "♣"
},
"cocktail": {
@@ -2387,20 +4834,52 @@
"category": "objects",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["alcohol", "beverage", "drink", "drunk", "cocktail", "mixed", "drink", "alcohol", "glass", "martini", "bar"],
+ "keywords": [
+ "alcohol",
+ "beverage",
+ "drink",
+ "drunk",
+ "cocktail",
+ "mixed",
+ "drink",
+ "alcohol",
+ "glass",
+ "martini",
+ "bar"
+ ],
"moji": "🍸"
},
"coffee": {
"unicode": "2615",
- "unicode_alternates": ["2615-FE0F"],
+ "unicode_alternates": [
+ "2615-FE0F"
+ ],
"name": "hot beverage",
"shortname": ":coffee:",
"category": "objects",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["beverage", "cafe", "drink", "espresso"],
+ "keywords": [
+ "beverage",
+ "cafe",
+ "drink",
+ "espresso"
+ ],
"moji": "☕"
},
+ "coffin": {
+ "unicode": "26B0",
+ "unicode_alternates": "",
+ "name": "coffin",
+ "shortname": ":coffin:",
+ "category": "objects",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": [
+ "death",
+ "object"
+ ]
+ },
"cold_sweat": {
"unicode": "1F630",
"unicode_alternates": [],
@@ -2409,9 +4888,28 @@
"category": "emoticons",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["face", "nervous", "sweat", "exasperated", "frustrated"],
+ "keywords": [
+ "face",
+ "nervous",
+ "sweat",
+ "exasperated",
+ "frustrated"
+ ],
"moji": "😰"
},
+ "comet": {
+ "unicode": "2604",
+ "unicode_alternates": "",
+ "name": "comet",
+ "shortname": ":comet:",
+ "category": "nature",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": [
+ "object",
+ "space"
+ ]
+ },
"compression": {
"unicode": "1F5DC",
"unicode_alternates": [],
@@ -2420,7 +4918,9 @@
"category": "objects_symbols",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["reduce"]
+ "keywords": [
+ "reduce"
+ ]
},
"computer": {
"unicode": "1F4BB",
@@ -2430,7 +4930,10 @@
"category": "objects",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["laptop", "tech"],
+ "keywords": [
+ "laptop",
+ "tech"
+ ],
"moji": "💻"
},
"computer_old": {
@@ -2439,9 +4942,14 @@
"name": "old personal computer",
"shortname": ":computer_old:",
"category": "objects_symbols",
- "aliases": [":old_personal_computer:"],
+ "aliases": [
+ ":old_personal_computer:"
+ ],
"aliases_ascii": [],
- "keywords": ["cpu", "terminal"]
+ "keywords": [
+ "cpu",
+ "terminal"
+ ]
},
"confetti_ball": {
"unicode": "1F38A",
@@ -2451,7 +4959,19 @@
"category": "objects",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["festival", "party", "party", "congratulations", "confetti", "ball", "celebrate", "win", "birthday", "new years", "wedding"],
+ "keywords": [
+ "festival",
+ "party",
+ "party",
+ "congratulations",
+ "confetti",
+ "ball",
+ "celebrate",
+ "win",
+ "birthday",
+ "new years",
+ "wedding"
+ ],
"moji": "🎊"
},
"confounded": {
@@ -2462,7 +4982,17 @@
"category": "emoticons",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["confused", "face", "sick", "unwell", "confound", "amaze", "perplex", "puzzle", "mystify"],
+ "keywords": [
+ "confused",
+ "face",
+ "sick",
+ "unwell",
+ "confound",
+ "amaze",
+ "perplex",
+ "puzzle",
+ "mystify"
+ ],
"moji": "😖"
},
"confused": {
@@ -2472,19 +5002,47 @@
"shortname": ":confused:",
"category": "emoticons",
"aliases": [],
- "aliases_ascii": [">:\\", ">:/", ":-/", ":-.", ":/", ":\\", "=/", "=\\", ":L", "=L"],
- "keywords": ["confused", "confuse", "daze", "perplex", "puzzle", "indifference", "skeptical", "undecided", "uneasy", "hesitant"],
+ "aliases_ascii": [
+ ">:\\",
+ ">:/",
+ ":-/",
+ ":-.",
+ ":/",
+ ":\\",
+ "=/",
+ "=\\",
+ ":L",
+ "=L"
+ ],
+ "keywords": [
+ "confused",
+ "confuse",
+ "daze",
+ "perplex",
+ "puzzle",
+ "indifference",
+ "skeptical",
+ "undecided",
+ "uneasy",
+ "hesitant"
+ ],
"moji": "😕"
},
"congratulations": {
"unicode": "3297",
- "unicode_alternates": ["3297-FE0F"],
+ "unicode_alternates": [
+ "3297-FE0F"
+ ],
"name": "circled ideograph congratulation",
"shortname": ":congratulations:",
"category": "other",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["chinese", "japanese", "kanji"],
+ "keywords": [
+ "chinese",
+ "japanese",
+ "kanji"
+ ],
"moji": "㊗"
},
"construction": {
@@ -2495,9 +5053,29 @@
"category": "places",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["caution", "progress", "wip"],
+ "keywords": [
+ "caution",
+ "progress",
+ "wip"
+ ],
"moji": "🚧"
},
+ "construction_site": {
+ "unicode": "1F3D7",
+ "unicode_alternates": "",
+ "name": "building construction",
+ "shortname": ":construction_site:",
+ "category": "travel",
+ "aliases": [
+ ":building_construction:"
+ ],
+ "aliases_ascii": [],
+ "keywords": [
+ "site",
+ "work",
+ "place"
+ ]
+ },
"construction_worker": {
"unicode": "1F477",
"unicode_alternates": [],
@@ -2506,9 +5084,89 @@
"category": "emoticons",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["human", "male", "man", "wip"],
+ "keywords": [
+ "human",
+ "male",
+ "man",
+ "wip"
+ ],
"moji": "👷"
},
+ "construction_worker_tone1": {
+ "unicode": "1F477-1F3FB",
+ "unicode_alternates": "",
+ "name": "construction worker tone 1",
+ "shortname": ":construction_worker_tone1:",
+ "category": "people",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": [
+ "human",
+ "male",
+ "man",
+ "wip"
+ ]
+ },
+ "construction_worker_tone2": {
+ "unicode": "1F477-1F3FC",
+ "unicode_alternates": "",
+ "name": "construction worker tone 2",
+ "shortname": ":construction_worker_tone2:",
+ "category": "people",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": [
+ "human",
+ "male",
+ "man",
+ "wip"
+ ]
+ },
+ "construction_worker_tone3": {
+ "unicode": "1F477-1F3FD",
+ "unicode_alternates": "",
+ "name": "construction worker tone 3",
+ "shortname": ":construction_worker_tone3:",
+ "category": "people",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": [
+ "human",
+ "male",
+ "man",
+ "wip"
+ ]
+ },
+ "construction_worker_tone4": {
+ "unicode": "1F477-1F3FE",
+ "unicode_alternates": "",
+ "name": "construction worker tone 4",
+ "shortname": ":construction_worker_tone4:",
+ "category": "people",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": [
+ "human",
+ "male",
+ "man",
+ "wip"
+ ]
+ },
+ "construction_worker_tone5": {
+ "unicode": "1F477-1F3FF",
+ "unicode_alternates": "",
+ "name": "construction worker tone 5",
+ "shortname": ":construction_worker_tone5:",
+ "category": "people",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": [
+ "human",
+ "male",
+ "man",
+ "wip"
+ ]
+ },
"control_knobs": {
"unicode": "1F39B",
"unicode_alternates": [],
@@ -2517,7 +5175,9 @@
"category": "objects_symbols",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["dial"]
+ "keywords": [
+ "dial"
+ ]
},
"contruction_site": {
"unicode": "1F3D7",
@@ -2525,9 +5185,14 @@
"name": "building construction",
"shortname": ":contruction_site:",
"category": "travel_places",
- "aliases": [":building_construction:"],
+ "aliases": [
+ ":building_construction:"
+ ],
"aliases_ascii": [],
- "keywords": ["site", "work"]
+ "keywords": [
+ "site",
+ "work"
+ ]
},
"convenience_store": {
"unicode": "1F3EA",
@@ -2537,7 +5202,9 @@
"category": "places",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["building"],
+ "keywords": [
+ "building"
+ ],
"moji": "🏪"
},
"cookie": {
@@ -2548,7 +5215,17 @@
"category": "objects",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["chocolate", "food", "oreo", "snack", "cookie", "dessert", "biscuit", "sweet", "chocolate"],
+ "keywords": [
+ "chocolate",
+ "food",
+ "oreo",
+ "snack",
+ "cookie",
+ "dessert",
+ "biscuit",
+ "sweet",
+ "chocolate"
+ ],
"moji": "🍪"
},
"cool": {
@@ -2559,7 +5236,10 @@
"category": "other",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["blue-square", "words"],
+ "keywords": [
+ "blue-square",
+ "words"
+ ],
"moji": "🆒"
},
"cop": {
@@ -2570,9 +5250,95 @@
"category": "emoticons",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["arrest", "enforcement", "law", "man", "police"],
+ "keywords": [
+ "arrest",
+ "enforcement",
+ "law",
+ "man",
+ "police"
+ ],
"moji": "👮"
},
+ "cop_tone1": {
+ "unicode": "1F46E-1F3FB",
+ "unicode_alternates": "",
+ "name": "police officer tone 1",
+ "shortname": ":cop_tone1:",
+ "category": "people",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": [
+ "arrest",
+ "enforcement",
+ "law",
+ "man",
+ "cop"
+ ]
+ },
+ "cop_tone2": {
+ "unicode": "1F46E-1F3FC",
+ "unicode_alternates": "",
+ "name": "police officer tone 2",
+ "shortname": ":cop_tone2:",
+ "category": "people",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": [
+ "arrest",
+ "enforcement",
+ "law",
+ "man",
+ "cop"
+ ]
+ },
+ "cop_tone3": {
+ "unicode": "1F46E-1F3FD",
+ "unicode_alternates": "",
+ "name": "police officer tone 3",
+ "shortname": ":cop_tone3:",
+ "category": "people",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": [
+ "arrest",
+ "enforcement",
+ "law",
+ "man",
+ "cop"
+ ]
+ },
+ "cop_tone4": {
+ "unicode": "1F46E-1F3FE",
+ "unicode_alternates": "",
+ "name": "police officer tone 4",
+ "shortname": ":cop_tone4:",
+ "category": "people",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": [
+ "arrest",
+ "enforcement",
+ "law",
+ "man",
+ "cop"
+ ]
+ },
+ "cop_tone5": {
+ "unicode": "1F46E-1F3FF",
+ "unicode_alternates": "",
+ "name": "police officer tone 5",
+ "shortname": ":cop_tone5:",
+ "category": "people",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": [
+ "arrest",
+ "enforcement",
+ "law",
+ "man",
+ "cop"
+ ]
+ },
"copyright": {
"moji": "©",
"unicode": "00A9",
@@ -2582,7 +5348,10 @@
"category": "other",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["ip", "license"]
+ "keywords": [
+ "ip",
+ "license"
+ ]
},
"corn": {
"unicode": "1F33D",
@@ -2592,7 +5361,22 @@
"category": "objects",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["food", "plant", "vegetable", "corn", "maize", "food", "iowa", "kernel", "popcorn", "husk", "yellow", "stalk", "cob", "ear"],
+ "keywords": [
+ "food",
+ "plant",
+ "vegetable",
+ "corn",
+ "maize",
+ "food",
+ "iowa",
+ "kernel",
+ "popcorn",
+ "husk",
+ "yellow",
+ "stalk",
+ "cob",
+ "ear"
+ ],
"moji": "🌽"
},
"couch": {
@@ -2601,9 +5385,20 @@
"name": "couch and lamp",
"shortname": ":couch:",
"category": "travel_places",
- "aliases": [":couch_and_lamp:"],
- "aliases_ascii": [],
- "keywords": ["lounge", "sectional", "sofa", "loveseat", "leather", "microfiber", "sit", "relax"]
+ "aliases": [
+ ":couch_and_lamp:"
+ ],
+ "aliases_ascii": [],
+ "keywords": [
+ "lounge",
+ "sectional",
+ "sofa",
+ "loveseat",
+ "leather",
+ "microfiber",
+ "sit",
+ "relax"
+ ]
},
"couple": {
"unicode": "1F46B",
@@ -2613,18 +5408,40 @@
"category": "emoticons",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["affection", "date", "dating", "human", "like", "love", "marriage", "people", "valentines"],
+ "keywords": [
+ "affection",
+ "date",
+ "dating",
+ "human",
+ "like",
+ "love",
+ "marriage",
+ "people",
+ "valentines"
+ ],
"moji": "👫"
},
"couple_mm": {
"unicode": "1F468-2764-1F468",
- "unicode_alternates": ["1F468-200D-2764-FE0F-200D-1F468"],
+ "unicode_alternates": [
+ "1F468-200D-2764-FE0F-200D-1F468"
+ ],
"name": "couple (man,man)",
"shortname": ":couple_mm:",
"category": "people",
- "aliases": [":couple_with_heart_mm:"],
- "aliases_ascii": [],
- "keywords": ["affection", "dating", "human", "like", "love", "marriage", "valentines"]
+ "aliases": [
+ ":couple_with_heart_mm:"
+ ],
+ "aliases_ascii": [],
+ "keywords": [
+ "affection",
+ "dating",
+ "human",
+ "like",
+ "love",
+ "marriage",
+ "valentines"
+ ]
},
"couple_with_heart": {
"unicode": "1F491",
@@ -2634,18 +5451,38 @@
"category": "emoticons",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["affection", "dating", "human", "like", "love", "marriage", "valentines"],
+ "keywords": [
+ "affection",
+ "dating",
+ "human",
+ "like",
+ "love",
+ "marriage",
+ "valentines"
+ ],
"moji": "💑"
},
"couple_ww": {
"unicode": "1F469-2764-1F469",
- "unicode_alternates": ["1F469-200D-2764-FE0F-200D-1F469"],
+ "unicode_alternates": [
+ "1F469-200D-2764-FE0F-200D-1F469"
+ ],
"name": "couple (woman,woman)",
"shortname": ":couple_ww:",
"category": "people",
- "aliases": [":couple_with_heart_ww:"],
- "aliases_ascii": [],
- "keywords": ["affection", "dating", "human", "like", "love", "marriage", "valentines"]
+ "aliases": [
+ ":couple_with_heart_ww:"
+ ],
+ "aliases_ascii": [],
+ "keywords": [
+ "affection",
+ "dating",
+ "human",
+ "like",
+ "love",
+ "marriage",
+ "valentines"
+ ]
},
"couplekiss": {
"unicode": "1F48F",
@@ -2655,7 +5492,13 @@
"category": "emoticons",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["dating", "like", "love", "marriage", "valentines"],
+ "keywords": [
+ "dating",
+ "like",
+ "love",
+ "marriage",
+ "valentines"
+ ],
"moji": "💏"
},
"cow": {
@@ -2666,7 +5509,11 @@
"category": "nature",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["animal", "beef", "ox"],
+ "keywords": [
+ "animal",
+ "beef",
+ "ox"
+ ],
"moji": "🐮"
},
"cow2": {
@@ -2677,18 +5524,46 @@
"category": "nature",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["animal", "beef", "nature", "ox", "cow", "milk", "dairy", "beef", "bessie", "moo"],
+ "keywords": [
+ "animal",
+ "beef",
+ "nature",
+ "ox",
+ "cow",
+ "milk",
+ "dairy",
+ "beef",
+ "bessie",
+ "moo"
+ ],
"moji": "🐄"
},
+ "crab": {
+ "unicode": "1F980",
+ "unicode_alternates": "",
+ "name": "crab",
+ "shortname": ":crab:",
+ "category": "nature",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": []
+ },
"crayon": {
"unicode": "1F58D",
"unicode_alternates": [],
"name": "lower left crayon",
"shortname": ":crayon:",
"category": "objects_symbols",
- "aliases": [":lower_left_crayon:"],
+ "aliases": [
+ ":lower_left_crayon:"
+ ],
"aliases_ascii": [],
- "keywords": ["write", "draw", "color", "wax"]
+ "keywords": [
+ "write",
+ "draw",
+ "color",
+ "wax"
+ ]
},
"credit_card": {
"unicode": "1F4B3",
@@ -2698,7 +5573,23 @@
"category": "objects",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["bill", "dollar", "money", "pay", "payment", "credit", "card", "loan", "purchase", "shopping", "mastercard", "visa", "american express", "wallet", "signature"],
+ "keywords": [
+ "bill",
+ "dollar",
+ "money",
+ "pay",
+ "payment",
+ "credit",
+ "card",
+ "loan",
+ "purchase",
+ "shopping",
+ "mastercard",
+ "visa",
+ "american express",
+ "wallet",
+ "signature"
+ ],
"moji": "💳"
},
"crescent_moon": {
@@ -2709,9 +5600,30 @@
"category": "nature",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["night", "moon", "crescent", "waxing", "sky", "night", "cheese", "phase"],
+ "keywords": [
+ "night",
+ "moon",
+ "crescent",
+ "waxing",
+ "sky",
+ "night",
+ "cheese",
+ "phase"
+ ],
"moji": "🌙"
},
+ "cricket": {
+ "unicode": "1F3CF",
+ "unicode_alternates": "",
+ "name": "cricket bat and ball",
+ "shortname": ":cricket:",
+ "category": "activity",
+ "aliases": [
+ ":cricket_bat_ball:"
+ ],
+ "aliases_ascii": [],
+ "keywords": []
+ },
"crocodile": {
"unicode": "1F40A",
"unicode_alternates": [],
@@ -2720,18 +5632,47 @@
"category": "nature",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["animal", "nature", "crocodile", "croc", "alligator", "gator", "cranky"],
+ "keywords": [
+ "animal",
+ "nature",
+ "crocodile",
+ "croc",
+ "alligator",
+ "gator",
+ "cranky"
+ ],
"moji": "🐊"
},
+ "cross": {
+ "unicode": "271D",
+ "unicode_alternates": "",
+ "name": "latin cross",
+ "shortname": ":cross:",
+ "category": "symbols",
+ "aliases": [
+ ":latin_cross:"
+ ],
+ "aliases_ascii": [],
+ "keywords": [
+ "religion",
+ "symbol",
+ "christian"
+ ]
+ },
"cross_heavy": {
"unicode": "1F547",
"unicode_alternates": [],
"name": "heavy latin cross",
"shortname": ":cross_heavy:",
"category": "objects_symbols",
- "aliases": [":heavy_latin_cross:"],
+ "aliases": [
+ ":heavy_latin_cross:"
+ ],
"aliases_ascii": [],
- "keywords": ["religion", "symbol"]
+ "keywords": [
+ "religion",
+ "symbol"
+ ]
},
"cross_white": {
"unicode": "1F546",
@@ -2739,9 +5680,14 @@
"name": "white latin cross",
"shortname": ":cross_white:",
"category": "objects_symbols",
- "aliases": [":white_latin_cross:"],
+ "aliases": [
+ ":white_latin_cross:"
+ ],
"aliases_ascii": [],
- "keywords": ["religion", "symbol"]
+ "keywords": [
+ "religion",
+ "symbol"
+ ]
},
"crossbones": {
"unicode": "1F571",
@@ -2749,9 +5695,15 @@
"name": "black skull and crossbones",
"shortname": ":crossbones:",
"category": "objects_symbols",
- "aliases": [":black_skull_and_crossbones:"],
+ "aliases": [
+ ":black_skull_and_crossbones:"
+ ],
"aliases_ascii": [],
- "keywords": ["poison", "danger", "death"]
+ "keywords": [
+ "poison",
+ "danger",
+ "death"
+ ]
},
"crossed_flags": {
"unicode": "1F38C",
@@ -2761,9 +5713,24 @@
"category": "objects",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["japan"],
+ "keywords": [
+ "japan"
+ ],
"moji": "🎌"
},
+ "crossed_swords": {
+ "unicode": "2694",
+ "unicode_alternates": "",
+ "name": "crossed swords",
+ "shortname": ":crossed_swords:",
+ "category": "objects",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": [
+ "object",
+ "weapon"
+ ]
+ },
"crown": {
"unicode": "1F451",
"unicode_alternates": [],
@@ -2772,7 +5739,12 @@
"category": "emoticons",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["king", "kod", "leader", "royalty"],
+ "keywords": [
+ "king",
+ "kod",
+ "leader",
+ "royalty"
+ ],
"moji": "👑"
},
"cruise_ship": {
@@ -2781,9 +5753,15 @@
"name": "passenger ship",
"shortname": ":cruise_ship:",
"category": "travel_places",
- "aliases": [":passenger_ship:"],
+ "aliases": [
+ ":passenger_ship:"
+ ],
"aliases_ascii": [],
- "keywords": ["titanic", "transportation", "boat"]
+ "keywords": [
+ "titanic",
+ "transportation",
+ "boat"
+ ]
},
"cry": {
"unicode": "1F622",
@@ -2792,8 +5770,21 @@
"shortname": ":cry:",
"category": "emoticons",
"aliases": [],
- "aliases_ascii": [":'(", ":'-(", ";(", ";-("],
- "keywords": ["face", "sad", "sad", "cry", "tear", "weep", "tears"],
+ "aliases_ascii": [
+ ":'(",
+ ":'-(",
+ ";(",
+ ";-("
+ ],
+ "keywords": [
+ "face",
+ "sad",
+ "sad",
+ "cry",
+ "tear",
+ "weep",
+ "tears"
+ ],
"moji": "😢"
},
"crying_cat_face": {
@@ -2804,7 +5795,22 @@
"category": "emoticons",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["animal", "cats", "sad", "tears", "weep", "cry", "cat", "sob", "tears", "sad", "melancholy", "morn", "somber", "hurt"],
+ "keywords": [
+ "animal",
+ "cats",
+ "sad",
+ "tears",
+ "weep",
+ "cry",
+ "cat",
+ "sob",
+ "tears",
+ "sad",
+ "melancholy",
+ "morn",
+ "somber",
+ "hurt"
+ ],
"moji": "😿"
},
"crystal_ball": {
@@ -2815,7 +5821,10 @@
"category": "objects",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["disco", "party"],
+ "keywords": [
+ "disco",
+ "party"
+ ],
"moji": "🔮"
},
"cupid": {
@@ -2826,7 +5835,13 @@
"category": "emoticons",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["affection", "heart", "like", "love", "valentines"],
+ "keywords": [
+ "affection",
+ "heart",
+ "like",
+ "love",
+ "valentines"
+ ],
"moji": "💘"
},
"curly_loop": {
@@ -2837,7 +5852,9 @@
"category": "other",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["scribble"],
+ "keywords": [
+ "scribble"
+ ],
"moji": "➰"
},
"currency_exchange": {
@@ -2848,7 +5865,11 @@
"category": "other",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["dollar", "money", "travel"],
+ "keywords": [
+ "dollar",
+ "money",
+ "travel"
+ ],
"moji": "💱"
},
"curry": {
@@ -2859,7 +5880,17 @@
"category": "objects",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["food", "hot", "indian", "spicy", "curry", "spice", "flavor", "food", "meal"],
+ "keywords": [
+ "food",
+ "hot",
+ "indian",
+ "spicy",
+ "curry",
+ "spice",
+ "flavor",
+ "food",
+ "meal"
+ ],
"moji": "🍛"
},
"custard": {
@@ -2870,7 +5901,18 @@
"category": "objects",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["desert", "food", "custard", "cream", "rich", "butter", "dessert", "crème", "brûlée", "french"],
+ "keywords": [
+ "desert",
+ "food",
+ "custard",
+ "cream",
+ "rich",
+ "butter",
+ "dessert",
+ "crème",
+ "brûlée",
+ "french"
+ ],
"moji": "🍮"
},
"customs": {
@@ -2881,7 +5923,17 @@
"category": "other",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["border", "passport", "customs", "travel", "foreign", "goods", "check", "authority", "government"],
+ "keywords": [
+ "border",
+ "passport",
+ "customs",
+ "travel",
+ "foreign",
+ "goods",
+ "check",
+ "authority",
+ "government"
+ ],
"moji": "🛃"
},
"cyclone": {
@@ -2893,7 +5945,17 @@
"category": "nature",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["blue", "cloud", "swirl", "weather", "cyclone", "hurricane", "typhoon", "storm", "ocean"]
+ "keywords": [
+ "blue",
+ "cloud",
+ "swirl",
+ "weather",
+ "cyclone",
+ "hurricane",
+ "typhoon",
+ "storm",
+ "ocean"
+ ]
},
"dagger": {
"unicode": "1F5E1",
@@ -2901,9 +5963,14 @@
"name": "dagger knife",
"shortname": ":dagger:",
"category": "objects_symbols",
- "aliases": [":dagger_knife:"],
+ "aliases": [
+ ":dagger_knife:"
+ ],
"aliases_ascii": [],
- "keywords": ["blade", "knife"]
+ "keywords": [
+ "blade",
+ "knife"
+ ]
},
"dancer": {
"unicode": "1F483",
@@ -2913,9 +5980,145 @@
"category": "emoticons",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["female", "fun", "girl", "woman", "dance", "dancer", "dress", "fancy", "boogy", "party", "celebrate", "ballet", "tango", "cha cha", "music"],
+ "keywords": [
+ "female",
+ "fun",
+ "girl",
+ "woman",
+ "dance",
+ "dancer",
+ "dress",
+ "fancy",
+ "boogy",
+ "party",
+ "celebrate",
+ "ballet",
+ "tango",
+ "cha cha",
+ "music"
+ ],
"moji": "💃"
},
+ "dancer_tone1": {
+ "unicode": "1F483-1F3FB",
+ "unicode_alternates": "",
+ "name": "dancer tone 1",
+ "shortname": ":dancer_tone1:",
+ "category": "people",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": [
+ "female",
+ "fun",
+ "girl",
+ "woman",
+ "dress",
+ "fancy",
+ "boogy",
+ "party",
+ "celebrate",
+ "ballet",
+ "tango",
+ "cha cha",
+ "music"
+ ]
+ },
+ "dancer_tone2": {
+ "unicode": "1F483-1F3FC",
+ "unicode_alternates": "",
+ "name": "dancer tone 2",
+ "shortname": ":dancer_tone2:",
+ "category": "people",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": [
+ "female",
+ "fun",
+ "girl",
+ "woman",
+ "dress",
+ "fancy",
+ "boogy",
+ "party",
+ "celebrate",
+ "ballet",
+ "tango",
+ "cha cha",
+ "music"
+ ]
+ },
+ "dancer_tone3": {
+ "unicode": "1F483-1F3FD",
+ "unicode_alternates": "",
+ "name": "dancer tone 3",
+ "shortname": ":dancer_tone3:",
+ "category": "people",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": [
+ "female",
+ "fun",
+ "girl",
+ "woman",
+ "dress",
+ "fancy",
+ "boogy",
+ "party",
+ "celebrate",
+ "ballet",
+ "tango",
+ "cha cha",
+ "music"
+ ]
+ },
+ "dancer_tone4": {
+ "unicode": "1F483-1F3FE",
+ "unicode_alternates": "",
+ "name": "dancer tone 4",
+ "shortname": ":dancer_tone4:",
+ "category": "people",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": [
+ "female",
+ "fun",
+ "girl",
+ "woman",
+ "dress",
+ "fancy",
+ "boogy",
+ "party",
+ "celebrate",
+ "ballet",
+ "tango",
+ "cha cha",
+ "music"
+ ]
+ },
+ "dancer_tone5": {
+ "unicode": "1F483-1F3FF",
+ "unicode_alternates": "",
+ "name": "dancer tone 5",
+ "shortname": ":dancer_tone5:",
+ "category": "people",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": [
+ "female",
+ "fun",
+ "girl",
+ "woman",
+ "dress",
+ "fancy",
+ "boogy",
+ "party",
+ "celebrate",
+ "ballet",
+ "tango",
+ "cha cha",
+ "music"
+ ]
+ },
"dancers": {
"unicode": "1F46F",
"unicode_alternates": [],
@@ -2924,7 +6127,19 @@
"category": "emoticons",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["bunny", "female", "girls", "women", "dancing", "dancers", "showgirl", "playboy", "costume", "bunny", "cancan"],
+ "keywords": [
+ "bunny",
+ "female",
+ "girls",
+ "women",
+ "dancing",
+ "dancers",
+ "showgirl",
+ "playboy",
+ "costume",
+ "bunny",
+ "cancan"
+ ],
"moji": "👯"
},
"dango": {
@@ -2935,7 +6150,15 @@
"category": "objects",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["food", "dango", "japanese", "dumpling", "mochi", "balls", "skewer"],
+ "keywords": [
+ "food",
+ "dango",
+ "japanese",
+ "dumpling",
+ "mochi",
+ "balls",
+ "skewer"
+ ],
"moji": "🍡"
},
"dark_sunglasses": {
@@ -2946,7 +6169,10 @@
"category": "objects_symbols",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["shades", "eyes"]
+ "keywords": [
+ "shades",
+ "eyes"
+ ]
},
"dart": {
"unicode": "1F3AF",
@@ -2956,7 +6182,19 @@
"category": "objects",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["bar", "game", "direct", "hit", "bullseye", "dart", "archery", "game", "fletching", "arrow", "sport"],
+ "keywords": [
+ "bar",
+ "game",
+ "direct",
+ "hit",
+ "bullseye",
+ "dart",
+ "archery",
+ "game",
+ "fletching",
+ "arrow",
+ "sport"
+ ],
"moji": "🎯"
},
"dash": {
@@ -2967,7 +6205,12 @@
"category": "emoticons",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["air", "fast", "shoo", "wind"],
+ "keywords": [
+ "air",
+ "fast",
+ "shoo",
+ "wind"
+ ],
"moji": "💨"
},
"date": {
@@ -2978,7 +6221,10 @@
"category": "objects",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["calendar", "schedule"],
+ "keywords": [
+ "calendar",
+ "schedule"
+ ],
"moji": "📅"
},
"deciduous_tree": {
@@ -2989,7 +6235,15 @@
"category": "nature",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["nature", "plant", "deciduous", "tree", "leaves", "fall", "color"],
+ "keywords": [
+ "nature",
+ "plant",
+ "deciduous",
+ "tree",
+ "leaves",
+ "fall",
+ "color"
+ ],
"moji": "🌳"
},
"department_store": {
@@ -3000,7 +6254,16 @@
"category": "places",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["building", "mall", "shopping", "department", "store", "retail", "sale", "merchandise"],
+ "keywords": [
+ "building",
+ "mall",
+ "shopping",
+ "department",
+ "store",
+ "retail",
+ "sale",
+ "merchandise"
+ ],
"moji": "🏬"
},
"descending_notes": {
@@ -3011,7 +6274,12 @@
"category": "objects_symbols",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["score", "music", "sound", "tone"]
+ "keywords": [
+ "score",
+ "music",
+ "sound",
+ "tone"
+ ]
},
"desert": {
"unicode": "1F3DC",
@@ -3021,7 +6289,14 @@
"category": "travel_places",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["hot", "dry", "sandy", "cactus", "sunny", "barren"]
+ "keywords": [
+ "hot",
+ "dry",
+ "sandy",
+ "cactus",
+ "sunny",
+ "barren"
+ ]
},
"desktop": {
"unicode": "1F5A5",
@@ -3029,9 +6304,13 @@
"name": "desktop computer",
"shortname": ":desktop:",
"category": "objects_symbols",
- "aliases": [":desktop_computer:"],
+ "aliases": [
+ ":desktop_computer:"
+ ],
"aliases_ascii": [],
- "keywords": ["cpu"]
+ "keywords": [
+ "cpu"
+ ]
},
"desktop_window": {
"unicode": "1F5D4",
@@ -3041,7 +6320,9 @@
"category": "objects_symbols",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["computer"]
+ "keywords": [
+ "computer"
+ ]
},
"diamond_shape_with_a_dot_inside": {
"unicode": "1F4A0",
@@ -3051,18 +6332,31 @@
"category": "other",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["diamond", "cute", "cuteness", "kawaii", "japanese", "glyph", "adorable"],
+ "keywords": [
+ "diamond",
+ "cute",
+ "cuteness",
+ "kawaii",
+ "japanese",
+ "glyph",
+ "adorable"
+ ],
"moji": "💠"
},
"diamonds": {
"unicode": "2666",
- "unicode_alternates": ["2666-FE0F"],
+ "unicode_alternates": [
+ "2666-FE0F"
+ ],
"name": "black diamond suit",
"shortname": ":diamonds:",
"category": "other",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["cards", "poker"],
+ "keywords": [
+ "cards",
+ "poker"
+ ],
"moji": "♦"
},
"disappointed": {
@@ -3072,8 +6366,24 @@
"shortname": ":disappointed:",
"category": "emoticons",
"aliases": [],
- "aliases_ascii": [">:[", ":-(", ":(", ":-[", ":[", "=("],
- "keywords": ["disappointed", "disappoint", "frown", "depressed", "discouraged", "face", "sad", "upset"],
+ "aliases_ascii": [
+ ">:[",
+ ":-(",
+ ":(",
+ ":-[",
+ ":[",
+ "=("
+ ],
+ "keywords": [
+ "disappointed",
+ "disappoint",
+ "frown",
+ "depressed",
+ "discouraged",
+ "face",
+ "sad",
+ "upset"
+ ],
"moji": "😞"
},
"disappointed_relieved": {
@@ -3084,7 +6394,14 @@
"category": "emoticons",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["face", "nervous", "phew", "sweat", "disappoint", "relief"],
+ "keywords": [
+ "face",
+ "nervous",
+ "phew",
+ "sweat",
+ "disappoint",
+ "relief"
+ ],
"moji": "😥"
},
"dividers": {
@@ -3093,9 +6410,14 @@
"name": "card index dividers",
"shortname": ":dividers:",
"category": "objects_symbols",
- "aliases": [":card_index_dividers:"],
+ "aliases": [
+ ":card_index_dividers:"
+ ],
"aliases_ascii": [],
- "keywords": ["stationery", "rolodex"]
+ "keywords": [
+ "stationery",
+ "rolodex"
+ ]
},
"dizzy": {
"unicode": "1F4AB",
@@ -3105,7 +6427,18 @@
"category": "emoticons",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["shoot", "sparkle", "star", "dizzy", "drunk", "sick", "intoxicated", "squeans", "starburst", "star"],
+ "keywords": [
+ "shoot",
+ "sparkle",
+ "star",
+ "dizzy",
+ "drunk",
+ "sick",
+ "intoxicated",
+ "squeans",
+ "starburst",
+ "star"
+ ],
"moji": "💫"
},
"dizzy_face": {
@@ -3115,8 +6448,23 @@
"shortname": ":dizzy_face:",
"category": "emoticons",
"aliases": [],
- "aliases_ascii": ["#-)", "#)", "%-)", "%)", "X)", "X-)"],
- "keywords": ["dizzy", "drunk", "inebriated", "face", "spent", "unconscious", "xox"],
+ "aliases_ascii": [
+ "#-)",
+ "#)",
+ "%-)",
+ "%)",
+ "X)",
+ "X-)"
+ ],
+ "keywords": [
+ "dizzy",
+ "drunk",
+ "inebriated",
+ "face",
+ "spent",
+ "unconscious",
+ "xox"
+ ],
"moji": "😵"
},
"do_not_litter": {
@@ -3127,7 +6475,17 @@
"category": "other",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["bin", "garbage", "trash", "litter", "garbage", "waste", "no", "can", "trash"],
+ "keywords": [
+ "bin",
+ "garbage",
+ "trash",
+ "litter",
+ "garbage",
+ "waste",
+ "no",
+ "can",
+ "trash"
+ ],
"moji": "🚯"
},
"document": {
@@ -3138,7 +6496,9 @@
"category": "objects_symbols",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["page"]
+ "keywords": [
+ "page"
+ ]
},
"document_text": {
"unicode": "1F5B9",
@@ -3146,9 +6506,13 @@
"name": "document with text",
"shortname": ":document_text:",
"category": "objects_symbols",
- "aliases": [":document_with_text:"],
+ "aliases": [
+ ":document_with_text:"
+ ],
"aliases_ascii": [],
- "keywords": ["page"]
+ "keywords": [
+ "page"
+ ]
},
"dog": {
"unicode": "1F436",
@@ -3158,7 +6522,12 @@
"category": "nature",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["animal", "friend", "nature", "woof"],
+ "keywords": [
+ "animal",
+ "friend",
+ "nature",
+ "woof"
+ ],
"moji": "🐶"
},
"dog2": {
@@ -3169,7 +6538,20 @@
"category": "nature",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["animal", "doge", "friend", "nature", "pet", "dog", "puppy", "pet", "friend", "woof", "bark", "fido"],
+ "keywords": [
+ "animal",
+ "doge",
+ "friend",
+ "nature",
+ "pet",
+ "dog",
+ "puppy",
+ "pet",
+ "friend",
+ "woof",
+ "bark",
+ "fido"
+ ],
"moji": "🐕"
},
"dollar": {
@@ -3180,7 +6562,21 @@
"category": "objects",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["bill", "currency", "money", "dollar", "united states", "canada", "australia", "banknote", "money", "currency", "paper", "cash", "bills"],
+ "keywords": [
+ "bill",
+ "currency",
+ "money",
+ "dollar",
+ "united states",
+ "canada",
+ "australia",
+ "banknote",
+ "money",
+ "currency",
+ "paper",
+ "cash",
+ "bills"
+ ],
"moji": "💵"
},
"dolls": {
@@ -3191,7 +6587,23 @@
"category": "objects",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["japanese", "kimono", "toy", "dolls", "japan", "japanese", "day", "girls", "emperor", "empress", "pray", "blessing", "imperial", "family", "royal"],
+ "keywords": [
+ "japanese",
+ "kimono",
+ "toy",
+ "dolls",
+ "japan",
+ "japanese",
+ "day",
+ "girls",
+ "emperor",
+ "empress",
+ "pray",
+ "blessing",
+ "imperial",
+ "family",
+ "royal"
+ ],
"moji": "🎎"
},
"dolphin": {
@@ -3202,7 +6614,15 @@
"category": "nature",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["animal", "fins", "fish", "flipper", "nature", "ocean", "sea"],
+ "keywords": [
+ "animal",
+ "fins",
+ "fish",
+ "flipper",
+ "nature",
+ "ocean",
+ "sea"
+ ],
"moji": "🐬"
},
"door": {
@@ -3213,7 +6633,17 @@
"category": "objects",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["entry", "exit", "house", "door", "doorway", "entrance", "enter", "exit", "entry"],
+ "keywords": [
+ "entry",
+ "exit",
+ "house",
+ "door",
+ "doorway",
+ "entrance",
+ "enter",
+ "exit",
+ "entry"
+ ],
"moji": "🚪"
},
"doughnut": {
@@ -3224,7 +6654,21 @@
"category": "objects",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["desert", "food", "snack", "sweet", "doughnut", "donut", "pastry", "fried", "dessert", "breakfast", "police", "homer", "sweet"],
+ "keywords": [
+ "desert",
+ "food",
+ "snack",
+ "sweet",
+ "doughnut",
+ "donut",
+ "pastry",
+ "fried",
+ "dessert",
+ "breakfast",
+ "police",
+ "homer",
+ "sweet"
+ ],
"moji": "🍩"
},
"dove": {
@@ -3233,9 +6677,14 @@
"name": "dove of peace",
"shortname": ":dove:",
"category": "objects_symbols",
- "aliases": [":dove_of_peace:"],
+ "aliases": [
+ ":dove_of_peace:"
+ ],
"aliases_ascii": [],
- "keywords": ["symbol", "bird"]
+ "keywords": [
+ "symbol",
+ "bird"
+ ]
},
"dragon": {
"unicode": "1F409",
@@ -3245,7 +6694,17 @@
"category": "nature",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["animal", "chinese", "green", "myth", "nature", "dragon", "fire", "legendary", "myth"],
+ "keywords": [
+ "animal",
+ "chinese",
+ "green",
+ "myth",
+ "nature",
+ "dragon",
+ "fire",
+ "legendary",
+ "myth"
+ ],
"moji": "🐉"
},
"dragon_face": {
@@ -3256,7 +6715,18 @@
"category": "nature",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["animal", "chinese", "green", "myth", "nature", "dragon", "head", "fire", "legendary", "myth"],
+ "keywords": [
+ "animal",
+ "chinese",
+ "green",
+ "myth",
+ "nature",
+ "dragon",
+ "head",
+ "fire",
+ "legendary",
+ "myth"
+ ],
"moji": "🐲"
},
"dress": {
@@ -3267,7 +6737,10 @@
"category": "emoticons",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["clothes", "fashion"],
+ "keywords": [
+ "clothes",
+ "fashion"
+ ],
"moji": "👗"
},
"dromedary_camel": {
@@ -3278,7 +6751,22 @@
"category": "nature",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["animal", "desert", "hot", "dromedary", "camel", "hump", "desert", "middle east", "heat", "hot", "water", "hump day", "wednesday", "sex"],
+ "keywords": [
+ "animal",
+ "desert",
+ "hot",
+ "dromedary",
+ "camel",
+ "hump",
+ "desert",
+ "middle east",
+ "heat",
+ "hot",
+ "water",
+ "hump day",
+ "wednesday",
+ "sex"
+ ],
"moji": "🐪"
},
"droplet": {
@@ -3289,7 +6777,23 @@
"category": "emoticons",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["drip", "faucet", "water", "drop", "droplet", "h20", "water", "aqua", "tear", "sweat", "rain", "moisture", "wet", "moist", "spit"],
+ "keywords": [
+ "drip",
+ "faucet",
+ "water",
+ "drop",
+ "droplet",
+ "h20",
+ "water",
+ "aqua",
+ "tear",
+ "sweat",
+ "rain",
+ "moisture",
+ "wet",
+ "moist",
+ "spit"
+ ],
"moji": "💧"
},
"dvd": {
@@ -3300,7 +6804,11 @@
"category": "objects",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["cd", "disc", "disk"],
+ "keywords": [
+ "cd",
+ "disc",
+ "disk"
+ ],
"moji": "📀"
},
"e-mail": {
@@ -3309,9 +6817,14 @@
"name": "e-mail symbol",
"shortname": ":e-mail:",
"category": "objects",
- "aliases": [":email:"],
- "aliases_ascii": [],
- "keywords": ["communication", "inbox"],
+ "aliases": [
+ ":email:"
+ ],
+ "aliases_ascii": [],
+ "keywords": [
+ "communication",
+ "inbox"
+ ],
"moji": "📧"
},
"ear": {
@@ -3322,7 +6835,12 @@
"category": "emoticons",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["face", "hear", "listen", "sound"],
+ "keywords": [
+ "face",
+ "hear",
+ "listen",
+ "sound"
+ ],
"moji": "👂"
},
"ear_of_rice": {
@@ -3333,9 +6851,87 @@
"category": "nature",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["nature", "plant", "ear", "rice", "food", "plant", "seed"],
+ "keywords": [
+ "nature",
+ "plant",
+ "ear",
+ "rice",
+ "food",
+ "plant",
+ "seed"
+ ],
"moji": "🌾"
},
+ "ear_tone1": {
+ "unicode": "1F442-1F3FB",
+ "unicode_alternates": "",
+ "name": "ear tone 1",
+ "shortname": ":ear_tone1:",
+ "category": "people",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": [
+ "hear",
+ "listen",
+ "sound"
+ ]
+ },
+ "ear_tone2": {
+ "unicode": "1F442-1F3FC",
+ "unicode_alternates": "",
+ "name": "ear tone 2",
+ "shortname": ":ear_tone2:",
+ "category": "people",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": [
+ "hear",
+ "listen",
+ "sound"
+ ]
+ },
+ "ear_tone3": {
+ "unicode": "1F442-1F3FD",
+ "unicode_alternates": "",
+ "name": "ear tone 3",
+ "shortname": ":ear_tone3:",
+ "category": "people",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": [
+ "hear",
+ "listen",
+ "sound"
+ ]
+ },
+ "ear_tone4": {
+ "unicode": "1F442-1F3FE",
+ "unicode_alternates": "",
+ "name": "ear tone 4",
+ "shortname": ":ear_tone4:",
+ "category": "people",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": [
+ "hear",
+ "listen",
+ "sound"
+ ]
+ },
+ "ear_tone5": {
+ "unicode": "1F442-1F3FF",
+ "unicode_alternates": "",
+ "name": "ear tone 5",
+ "shortname": ":ear_tone5:",
+ "category": "people",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": [
+ "hear",
+ "listen",
+ "sound"
+ ]
+ },
"earth_africa": {
"unicode": "1F30D",
"unicode_alternates": [],
@@ -3344,7 +6940,18 @@
"category": "nature",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["globe", "international", "world", "earth", "globe", "space", "planet", "africa", "europe", "home"],
+ "keywords": [
+ "globe",
+ "international",
+ "world",
+ "earth",
+ "globe",
+ "space",
+ "planet",
+ "africa",
+ "europe",
+ "home"
+ ],
"moji": "🌍"
},
"earth_americas": {
@@ -3355,7 +6962,21 @@
"category": "nature",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["USA", "globe", "international", "world", "earth", "globe", "space", "planet", "north", "south", "america", "americas", "home"],
+ "keywords": [
+ "USA",
+ "globe",
+ "international",
+ "world",
+ "earth",
+ "globe",
+ "space",
+ "planet",
+ "north",
+ "south",
+ "america",
+ "americas",
+ "home"
+ ],
"moji": "🌎"
},
"earth_asia": {
@@ -3366,7 +6987,19 @@
"category": "nature",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["east", "globe", "international", "world", "earth", "globe", "space", "planet", "asia", "australia", "home"],
+ "keywords": [
+ "east",
+ "globe",
+ "international",
+ "world",
+ "earth",
+ "globe",
+ "space",
+ "planet",
+ "asia",
+ "australia",
+ "home"
+ ],
"moji": "🌏"
},
"egg": {
@@ -3377,7 +7010,18 @@
"category": "objects",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["breakfast", "food", "egg", "fry", "pan", "flat", "cook", "frying", "cooking", "utensil"],
+ "keywords": [
+ "breakfast",
+ "food",
+ "egg",
+ "fry",
+ "pan",
+ "flat",
+ "cook",
+ "frying",
+ "cooking",
+ "utensil"
+ ],
"moji": "🍳"
},
"eggplant": {
@@ -3388,23 +7032,41 @@
"category": "objects",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["aubergine", "food", "nature", "vegetable", "eggplant", "aubergine", "fruit", "purple", "penis"],
+ "keywords": [
+ "aubergine",
+ "food",
+ "nature",
+ "vegetable",
+ "eggplant",
+ "aubergine",
+ "fruit",
+ "purple",
+ "penis"
+ ],
"moji": "🍆"
},
"eight": {
"moji": "8️⃣",
"unicode": "0038-20E3",
- "unicode_alternates": ["0038-FE0F-20E3"],
+ "unicode_alternates": [
+ "0038-FE0F-20E3"
+ ],
"name": "digit eight",
"shortname": ":eight:",
"category": "other",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["8", "blue-square", "numbers"]
+ "keywords": [
+ "8",
+ "blue-square",
+ "numbers"
+ ]
},
"eight_pointed_black_star": {
"unicode": "2734",
- "unicode_alternates": ["2734-FE0F"],
+ "unicode_alternates": [
+ "2734-FE0F"
+ ],
"name": "eight pointed black star",
"shortname": ":eight_pointed_black_star:",
"category": "other",
@@ -3415,13 +7077,19 @@
},
"eight_spoked_asterisk": {
"unicode": "2733",
- "unicode_alternates": ["2733-FE0F"],
+ "unicode_alternates": [
+ "2733-FE0F"
+ ],
"name": "eight spoked asterisk",
"shortname": ":eight_spoked_asterisk:",
"category": "other",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["green-square", "sparkle", "star"],
+ "keywords": [
+ "green-square",
+ "sparkle",
+ "star"
+ ],
"moji": "✳"
},
"electric_plug": {
@@ -3432,7 +7100,10 @@
"category": "objects",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["charger", "power"],
+ "keywords": [
+ "charger",
+ "power"
+ ],
"moji": "🔌"
},
"elephant": {
@@ -3443,7 +7114,12 @@
"category": "nature",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["animal", "nature", "nose", "thailand"],
+ "keywords": [
+ "animal",
+ "nature",
+ "nose",
+ "thailand"
+ ],
"moji": "🐘"
},
"end": {
@@ -3454,18 +7130,28 @@
"category": "other",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["arrow", "words"],
+ "keywords": [
+ "arrow",
+ "words"
+ ],
"moji": "🔚"
},
"envelope": {
"unicode": "2709",
- "unicode_alternates": ["2709-FE0F"],
+ "unicode_alternates": [
+ "2709-FE0F"
+ ],
"name": "envelope",
"shortname": ":envelope:",
"category": "objects",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["communication", "letter", "mail", "postal"],
+ "keywords": [
+ "communication",
+ "letter",
+ "mail",
+ "postal"
+ ],
"moji": "✉"
},
"envelope_back": {
@@ -3474,9 +7160,16 @@
"name": "back of envelope",
"shortname": ":envelope_back:",
"category": "objects_symbols",
- "aliases": [":back_of_envelope:"],
+ "aliases": [
+ ":back_of_envelope:"
+ ],
"aliases_ascii": [],
- "keywords": ["communication", "letter", "mail", "postal"]
+ "keywords": [
+ "communication",
+ "letter",
+ "mail",
+ "postal"
+ ]
},
"envelope_flying": {
"unicode": "1F585",
@@ -3484,9 +7177,16 @@
"name": "flying envelope",
"shortname": ":envelope_flying:",
"category": "objects_symbols",
- "aliases": [":flying_envelope:"],
+ "aliases": [
+ ":flying_envelope:"
+ ],
"aliases_ascii": [],
- "keywords": ["communication", "letter", "mail", "postal"]
+ "keywords": [
+ "communication",
+ "letter",
+ "mail",
+ "postal"
+ ]
},
"envelope_stamped": {
"unicode": "1F583",
@@ -3494,9 +7194,16 @@
"name": "stamped envelope",
"shortname": ":envelope_stamped:",
"category": "objects_symbols",
- "aliases": [":stamped_envelope:"],
+ "aliases": [
+ ":stamped_envelope:"
+ ],
"aliases_ascii": [],
- "keywords": ["communication", "letter", "mail", "postal"]
+ "keywords": [
+ "communication",
+ "letter",
+ "mail",
+ "postal"
+ ]
},
"envelope_stamped_pen": {
"unicode": "1F586",
@@ -3504,9 +7211,16 @@
"name": "pen over stamped envelope",
"shortname": ":envelope_stamped_pen:",
"category": "objects_symbols",
- "aliases": [":pen_over_stamped_envelope:"],
+ "aliases": [
+ ":pen_over_stamped_envelope:"
+ ],
"aliases_ascii": [],
- "keywords": ["communication", "letter", "mail", "postal"]
+ "keywords": [
+ "communication",
+ "letter",
+ "mail",
+ "postal"
+ ]
},
"envelope_with_arrow": {
"unicode": "1F4E9",
@@ -3516,7 +7230,9 @@
"category": "objects",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["email"],
+ "keywords": [
+ "email"
+ ],
"moji": "📩"
},
"euro": {
@@ -3527,7 +7243,19 @@
"category": "objects",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["currency", "dollar", "money", "euro", "europe", "banknote", "money", "currency", "paper", "cash", "bills"],
+ "keywords": [
+ "currency",
+ "dollar",
+ "money",
+ "euro",
+ "europe",
+ "banknote",
+ "money",
+ "currency",
+ "paper",
+ "cash",
+ "bills"
+ ],
"moji": "💶"
},
"european_castle": {
@@ -3538,7 +7266,29 @@
"category": "places",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["building", "history", "royalty", "castle", "european", "residence", "royalty", "disneyland", "disney", "fort", "fortified", "moat", "tower", "princess", "prince", "lord", "king", "queen", "fortress", "nobel", "stronghold"],
+ "keywords": [
+ "building",
+ "history",
+ "royalty",
+ "castle",
+ "european",
+ "residence",
+ "royalty",
+ "disneyland",
+ "disney",
+ "fort",
+ "fortified",
+ "moat",
+ "tower",
+ "princess",
+ "prince",
+ "lord",
+ "king",
+ "queen",
+ "fortress",
+ "nobel",
+ "stronghold"
+ ],
"moji": "🏰"
},
"european_post_office": {
@@ -3549,7 +7299,9 @@
"category": "places",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["building"],
+ "keywords": [
+ "building"
+ ],
"moji": "🏤"
},
"evergreen_tree": {
@@ -3560,18 +7312,29 @@
"category": "nature",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["nature", "plant", "evergreen", "tree", "needles", "christmas"],
+ "keywords": [
+ "nature",
+ "plant",
+ "evergreen",
+ "tree",
+ "needles",
+ "christmas"
+ ],
"moji": "🌲"
},
"exclamation": {
"unicode": "2757",
- "unicode_alternates": ["2757-FE0F"],
+ "unicode_alternates": [
+ "2757-FE0F"
+ ],
"name": "heavy exclamation mark symbol",
"shortname": ":exclamation:",
"category": "other",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["surprise"],
+ "keywords": [
+ "surprise"
+ ],
"moji": "❗"
},
"expressionless": {
@@ -3581,8 +7344,20 @@
"shortname": ":expressionless:",
"category": "emoticons",
"aliases": [],
- "aliases_ascii": ["-_-", "-__-", "-___-"],
- "keywords": ["expressionless", "blank", "void", "vapid", "without expression", "face", "indifferent"],
+ "aliases_ascii": [
+ "-_-",
+ "-__-",
+ "-___-"
+ ],
+ "keywords": [
+ "expressionless",
+ "blank",
+ "void",
+ "vapid",
+ "without expression",
+ "face",
+ "indifferent"
+ ],
"moji": "😑"
},
"eye": {
@@ -3593,7 +7368,21 @@
"category": "people",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["look", "peek", "watch"]
+ "keywords": [
+ "look",
+ "peek",
+ "watch"
+ ]
+ },
+ "eye_in_speech_bubble": {
+ "unicode": "1F441-1F5E8",
+ "unicode_alternates": "1f441-200d-1f5e8",
+ "name": "eye in speech bubble",
+ "shortname": ":eye_in_speech_bubble:",
+ "category": "symbols",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": []
},
"eyeglasses": {
"unicode": "1F453",
@@ -3603,7 +7392,24 @@
"category": "emoticons",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["accessories", "eyesight", "fashion", "eyeglasses", "spectacles", "eye", "sight", "nearsightedness", "myopia", "farsightedness", "hyperopia", "frames", "vision", "see", "blurry", "contacts"],
+ "keywords": [
+ "accessories",
+ "eyesight",
+ "fashion",
+ "eyeglasses",
+ "spectacles",
+ "eye",
+ "sight",
+ "nearsightedness",
+ "myopia",
+ "farsightedness",
+ "hyperopia",
+ "frames",
+ "vision",
+ "see",
+ "blurry",
+ "contacts"
+ ],
"moji": "👓"
},
"eyes": {
@@ -3614,7 +7420,12 @@
"category": "emoticons",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["look", "peek", "stalk", "watch"],
+ "keywords": [
+ "look",
+ "peek",
+ "stalk",
+ "watch"
+ ],
"moji": "👀"
},
"factory": {
@@ -3625,7 +7436,9 @@
"category": "places",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["building"],
+ "keywords": [
+ "building"
+ ],
"moji": "🏭"
},
"fallen_leaf": {
@@ -3636,7 +7449,17 @@
"category": "nature",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["leaves", "nature", "plant", "vegetable", "leaf", "fall", "color", "deciduous", "autumn"],
+ "keywords": [
+ "leaves",
+ "nature",
+ "plant",
+ "vegetable",
+ "leaf",
+ "fall",
+ "color",
+ "deciduous",
+ "autumn"
+ ],
"moji": "🍂"
},
"family": {
@@ -3647,148 +7470,359 @@
"category": "emoticons",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["child", "dad", "father", "home", "mom", "mother", "parents", "family", "mother", "father", "child", "girl", "boy", "group", "unit"],
+ "keywords": [
+ "child",
+ "dad",
+ "father",
+ "home",
+ "mom",
+ "mother",
+ "parents",
+ "family",
+ "mother",
+ "father",
+ "child",
+ "girl",
+ "boy",
+ "group",
+ "unit"
+ ],
"moji": "👪"
},
"family_mmb": {
"unicode": "1F468-1F468-1F466",
- "unicode_alternates": ["1F468-200D-1F468-200D-1F466"],
+ "unicode_alternates": [
+ "1F468-200D-1F468-200D-1F466"
+ ],
"name": "family (man,man,boy)",
"shortname": ":family_mmb:",
"category": "people",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["child", "dad", "father", "parents", "group", "unit", "gay", "homosexual", "man", "boy"]
+ "keywords": [
+ "child",
+ "dad",
+ "father",
+ "parents",
+ "group",
+ "unit",
+ "gay",
+ "homosexual",
+ "man",
+ "boy"
+ ]
},
"family_mmbb": {
"unicode": "1F468-1F468-1F466-1F466",
- "unicode_alternates": ["1F468-200D-1F468-200D-1F466-200D-1F466"],
+ "unicode_alternates": [
+ "1F468-200D-1F468-200D-1F466-200D-1F466"
+ ],
"name": "family (man,man,boy,boy)",
"shortname": ":family_mmbb:",
"category": "people",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["children", "dad", "father", "parents", "group", "unit", "gay", "homosexual", "man", "boy"]
+ "keywords": [
+ "children",
+ "dad",
+ "father",
+ "parents",
+ "group",
+ "unit",
+ "gay",
+ "homosexual",
+ "man",
+ "boy"
+ ]
},
"family_mmg": {
"unicode": "1F468-1F468-1F467",
- "unicode_alternates": ["1F468-200D-1F468-200D-1F467"],
+ "unicode_alternates": [
+ "1F468-200D-1F468-200D-1F467"
+ ],
"name": "family (man,man,girl)",
"shortname": ":family_mmg:",
"category": "people",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["child", "dad", "father", "parents", "group", "unit", "gay", "homosexual", "man", "girl"]
+ "keywords": [
+ "child",
+ "dad",
+ "father",
+ "parents",
+ "group",
+ "unit",
+ "gay",
+ "homosexual",
+ "man",
+ "girl"
+ ]
},
"family_mmgb": {
"unicode": "1F468-1F468-1F467-1F466",
- "unicode_alternates": ["1F468-200D-1F468-200D-1F467-200D-1F466"],
+ "unicode_alternates": [
+ "1F468-200D-1F468-200D-1F467-200D-1F466"
+ ],
"name": "family (man,man,girl,boy)",
"shortname": ":family_mmgb:",
"category": "people",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["children", "dad", "father", "parents", "group", "unit", "gay", "homosexual", "man", "girl", "boy"]
+ "keywords": [
+ "children",
+ "dad",
+ "father",
+ "parents",
+ "group",
+ "unit",
+ "gay",
+ "homosexual",
+ "man",
+ "girl",
+ "boy"
+ ]
},
"family_mmgg": {
"unicode": "1F468-1F468-1F467-1F467",
- "unicode_alternates": ["1F468-200D-1F468-200D-1F467-200D-1F467"],
+ "unicode_alternates": [
+ "1F468-200D-1F468-200D-1F467-200D-1F467"
+ ],
"name": "family (man,man,girl,girl)",
"shortname": ":family_mmgg:",
"category": "people",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["children", "dad", "father", "parents", "group", "unit", "gay", "homosexual", "man", "girl"]
+ "keywords": [
+ "children",
+ "dad",
+ "father",
+ "parents",
+ "group",
+ "unit",
+ "gay",
+ "homosexual",
+ "man",
+ "girl"
+ ]
},
"family_mwbb": {
"unicode": "1F468-1F469-1F466-1F466",
- "unicode_alternates": ["1F468-200D-1F469-200D-1F466-200D-1F466"],
+ "unicode_alternates": [
+ "1F468-200D-1F469-200D-1F466-200D-1F466"
+ ],
"name": "family (man,woman,boy,boy)",
"shortname": ":family_mwbb:",
"category": "people",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["dad", "father", "mom", "mother", "parents", "children", "boy", "group", "unit", "man", "woman"]
+ "keywords": [
+ "dad",
+ "father",
+ "mom",
+ "mother",
+ "parents",
+ "children",
+ "boy",
+ "group",
+ "unit",
+ "man",
+ "woman"
+ ]
},
"family_mwg": {
"unicode": "1F468-1F469-1F467",
- "unicode_alternates": ["1F468-200D-1F469-200D-1F467"],
+ "unicode_alternates": [
+ "1F468-200D-1F469-200D-1F467"
+ ],
"name": "family (man,woman,girl)",
"shortname": ":family_mwg:",
"category": "people",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["child", "dad", "father", "mom", "mother", "parents", "girl", "boy", "group", "unit", "man", "woman"]
+ "keywords": [
+ "child",
+ "dad",
+ "father",
+ "mom",
+ "mother",
+ "parents",
+ "girl",
+ "boy",
+ "group",
+ "unit",
+ "man",
+ "woman"
+ ]
},
"family_mwgb": {
"unicode": "1F468-1F469-1F467-1F466",
- "unicode_alternates": ["1F468-200D-1F469-200D-1F467-200D-1F466"],
+ "unicode_alternates": [
+ "1F468-200D-1F469-200D-1F467-200D-1F466"
+ ],
"name": "family (man,woman,girl,boy)",
"shortname": ":family_mwgb:",
"category": "people",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["dad", "father", "mom", "mother", "parents", "children", "girl", "boy", "group", "unit", "man", "woman"]
+ "keywords": [
+ "dad",
+ "father",
+ "mom",
+ "mother",
+ "parents",
+ "children",
+ "girl",
+ "boy",
+ "group",
+ "unit",
+ "man",
+ "woman"
+ ]
},
"family_mwgg": {
"unicode": "1F468-1F469-1F467-1F467",
- "unicode_alternates": ["1F468-200D-1F469-200D-1F467-200D-1F467"],
+ "unicode_alternates": [
+ "1F468-200D-1F469-200D-1F467-200D-1F467"
+ ],
"name": "family (man,woman,girl,girl)",
"shortname": ":family_mwgg:",
"category": "people",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["dad", "father", "mom", "mother", "parents", "children", "girl", "group", "unit", "man", "woman"]
+ "keywords": [
+ "dad",
+ "father",
+ "mom",
+ "mother",
+ "parents",
+ "children",
+ "girl",
+ "group",
+ "unit",
+ "man",
+ "woman"
+ ]
},
"family_wwb": {
"unicode": "1F469-1F469-1F466",
- "unicode_alternates": ["1F469-200D-1F469-200D-1F466"],
+ "unicode_alternates": [
+ "1F469-200D-1F469-200D-1F466"
+ ],
"name": "family (woman,woman,boy)",
"shortname": ":family_wwb:",
"category": "people",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["mom", "mother", "parents", "child", "boy", "group", "unit", "gay", "lesbian", "homosexual", "woman"]
+ "keywords": [
+ "mom",
+ "mother",
+ "parents",
+ "child",
+ "boy",
+ "group",
+ "unit",
+ "gay",
+ "lesbian",
+ "homosexual",
+ "woman"
+ ]
},
"family_wwbb": {
"unicode": "1F469-1F469-1F466-1F466",
- "unicode_alternates": ["1F469-200D-1F469-200D-1F466-200D-1F466"],
+ "unicode_alternates": [
+ "1F469-200D-1F469-200D-1F466-200D-1F466"
+ ],
"name": "family (woman,woman,boy,boy)",
"shortname": ":family_wwbb:",
"category": "people",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["mom", "mother", "parents", "children", "group", "unit", "gay", "lesbian", "homosexual", "woman", "boy"]
+ "keywords": [
+ "mom",
+ "mother",
+ "parents",
+ "children",
+ "group",
+ "unit",
+ "gay",
+ "lesbian",
+ "homosexual",
+ "woman",
+ "boy"
+ ]
},
"family_wwg": {
"unicode": "1F469-1F469-1F467",
- "unicode_alternates": ["1F469-200D-1F469-200D-1F467"],
+ "unicode_alternates": [
+ "1F469-200D-1F469-200D-1F467"
+ ],
"name": "family (woman,woman,girl)",
"shortname": ":family_wwg:",
"category": "people",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["mom", "mother", "parents", "child", "woman", "girl", "group", "unit", "gay", "lesbian", "homosexual"]
+ "keywords": [
+ "mom",
+ "mother",
+ "parents",
+ "child",
+ "woman",
+ "girl",
+ "group",
+ "unit",
+ "gay",
+ "lesbian",
+ "homosexual"
+ ]
},
"family_wwgb": {
"unicode": "1F469-1F469-1F467-1F466",
- "unicode_alternates": ["1F469-200D-1F469-200D-1F467-200D-1F466"],
+ "unicode_alternates": [
+ "1F469-200D-1F469-200D-1F467-200D-1F466"
+ ],
"name": "family (woman,woman,girl,boy)",
"shortname": ":family_wwgb:",
"category": "people",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["mom", "mother", "parents", "children", "group", "unit", "gay", "lesbian", "homosexual", "woman", "girl", "boy"]
+ "keywords": [
+ "mom",
+ "mother",
+ "parents",
+ "children",
+ "group",
+ "unit",
+ "gay",
+ "lesbian",
+ "homosexual",
+ "woman",
+ "girl",
+ "boy"
+ ]
},
"family_wwgg": {
"unicode": "1F469-1F469-1F467-1F467",
- "unicode_alternates": ["1F469-200D-1F469-200D-1F467-200D-1F467"],
+ "unicode_alternates": [
+ "1F469-200D-1F469-200D-1F467-200D-1F467"
+ ],
"name": "family (woman,woman,girl,girl)",
"shortname": ":family_wwgg:",
"category": "people",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["mom", "mother", "parents", "children", "group", "unit", "gay", "lesbian", "homosexual", "woman", "girl"]
+ "keywords": [
+ "mom",
+ "mother",
+ "parents",
+ "children",
+ "group",
+ "unit",
+ "gay",
+ "lesbian",
+ "homosexual",
+ "woman",
+ "girl"
+ ]
},
"fast_forward": {
"unicode": "23E9",
@@ -3798,7 +7832,9 @@
"category": "other",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["blue-square"],
+ "keywords": [
+ "blue-square"
+ ],
"moji": "⏩"
},
"fax": {
@@ -3809,7 +7845,10 @@
"category": "objects",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["communication", "technology"],
+ "keywords": [
+ "communication",
+ "technology"
+ ],
"moji": "📠"
},
"fearful": {
@@ -3820,7 +7859,17 @@
"category": "emoticons",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["face", "nervous", "oops", "scared", "terrified", "fear", "fearful", "scared", "frightened"],
+ "keywords": [
+ "face",
+ "nervous",
+ "oops",
+ "scared",
+ "terrified",
+ "fear",
+ "fearful",
+ "scared",
+ "frightened"
+ ],
"moji": "😨"
},
"feet": {
@@ -3831,7 +7880,29 @@
"category": "nature",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["animal", "cat", "dog", "footprints", "paw", "pet", "tracking", "paw", "prints", "mark", "imprints", "footsteps", "animal", "lion", "bear", "dog", "cat", "raccoon", "critter", "feet", "pawsteps"],
+ "keywords": [
+ "animal",
+ "cat",
+ "dog",
+ "footprints",
+ "paw",
+ "pet",
+ "tracking",
+ "paw",
+ "prints",
+ "mark",
+ "imprints",
+ "footsteps",
+ "animal",
+ "lion",
+ "bear",
+ "dog",
+ "cat",
+ "raccoon",
+ "critter",
+ "feet",
+ "pawsteps"
+ ],
"moji": "🐾"
},
"ferris_wheel": {
@@ -3842,9 +7913,44 @@
"category": "places",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["carnival", "londoneye", "photo", "farris", "wheel", "amusement", "park", "fair", "ride", "entertainment"],
+ "keywords": [
+ "carnival",
+ "londoneye",
+ "photo",
+ "farris",
+ "wheel",
+ "amusement",
+ "park",
+ "fair",
+ "ride",
+ "entertainment"
+ ],
"moji": "🎡"
},
+ "ferry": {
+ "unicode": "26F4",
+ "unicode_alternates": "",
+ "name": "ferry",
+ "shortname": ":ferry:",
+ "category": "travel",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": [
+ "boat",
+ "place",
+ "travel"
+ ]
+ },
+ "field_hockey": {
+ "unicode": "1F3D1",
+ "unicode_alternates": "",
+ "name": "field hockey stick and ball",
+ "shortname": ":field_hockey:",
+ "category": "activity",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": []
+ },
"file_cabinet": {
"unicode": "1F5C4",
"unicode_alternates": [],
@@ -3853,7 +7959,12 @@
"category": "objects_symbols",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["folders", "office", "documents", "storage"]
+ "keywords": [
+ "folders",
+ "office",
+ "documents",
+ "storage"
+ ]
},
"file_folder": {
"unicode": "1F4C1",
@@ -3863,7 +7974,9 @@
"category": "objects",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["documents"],
+ "keywords": [
+ "documents"
+ ],
"moji": "📁"
},
"film_frames": {
@@ -3874,7 +7987,14 @@
"category": "activity",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["movie", "record", "8mm", "16mm", "reel", "celluloid"]
+ "keywords": [
+ "movie",
+ "record",
+ "8mm",
+ "16mm",
+ "reel",
+ "celluloid"
+ ]
},
"finger_pointing_down": {
"unicode": "1F597",
@@ -3882,9 +8002,15 @@
"name": "white down pointing left hand index",
"shortname": ":finger_pointing_down:",
"category": "people",
- "aliases": [":white_down_pointing_left_hand_index:"],
+ "aliases": [
+ ":white_down_pointing_left_hand_index:"
+ ],
"aliases_ascii": [],
- "keywords": ["direction", "finger", "hand"]
+ "keywords": [
+ "direction",
+ "finger",
+ "hand"
+ ]
},
"finger_pointing_down2": {
"unicode": "1F59F",
@@ -3892,9 +8018,15 @@
"name": "sideways white down pointing index",
"shortname": ":finger_pointing_down2:",
"category": "people",
- "aliases": [":sideways_white_down_pointing_index:"],
+ "aliases": [
+ ":sideways_white_down_pointing_index:"
+ ],
"aliases_ascii": [],
- "keywords": ["direction", "finger", "hand"]
+ "keywords": [
+ "direction",
+ "finger",
+ "hand"
+ ]
},
"finger_pointing_left": {
"unicode": "1F598",
@@ -3902,9 +8034,15 @@
"name": "sideways white left pointing index",
"shortname": ":finger_pointing_left:",
"category": "people",
- "aliases": [":sideways_white_left_pointing_index:"],
+ "aliases": [
+ ":sideways_white_left_pointing_index:"
+ ],
"aliases_ascii": [],
- "keywords": ["direction", "finger", "hand"]
+ "keywords": [
+ "direction",
+ "finger",
+ "hand"
+ ]
},
"finger_pointing_right": {
"unicode": "1F599",
@@ -3912,9 +8050,15 @@
"name": "sideways white right pointing index",
"shortname": ":finger_pointing_right:",
"category": "people",
- "aliases": [":sideways_white_right_pointing_index:"],
+ "aliases": [
+ ":sideways_white_right_pointing_index:"
+ ],
"aliases_ascii": [],
- "keywords": ["direction", "finger", "hand"]
+ "keywords": [
+ "direction",
+ "finger",
+ "hand"
+ ]
},
"finger_pointing_up": {
"unicode": "1F59E",
@@ -3922,9 +8066,15 @@
"name": "sideways white up pointing index",
"shortname": ":finger_pointing_up:",
"category": "people",
- "aliases": [":sideways_white_up_pointing_index:"],
+ "aliases": [
+ ":sideways_white_up_pointing_index:"
+ ],
"aliases_ascii": [],
- "keywords": ["direction", "finger", "hand"]
+ "keywords": [
+ "direction",
+ "finger",
+ "hand"
+ ]
},
"fire": {
"unicode": "1F525",
@@ -3932,9 +8082,15 @@
"name": "fire",
"shortname": ":fire:",
"category": "emoticons",
- "aliases": [":flame:"],
- "aliases_ascii": [],
- "keywords": ["cook", "hot", "flame"],
+ "aliases": [
+ ":flame:"
+ ],
+ "aliases_ascii": [],
+ "keywords": [
+ "cook",
+ "hot",
+ "flame"
+ ],
"moji": "🔥"
},
"fire_engine": {
@@ -3945,7 +8101,17 @@
"category": "places",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["cars", "transportation", "vehicle", "fire", "fighter", "engine", "truck", "emergency", "medical"],
+ "keywords": [
+ "cars",
+ "transportation",
+ "vehicle",
+ "fire",
+ "fighter",
+ "engine",
+ "truck",
+ "emergency",
+ "medical"
+ ],
"moji": "🚒"
},
"fire_engine_oncoming": {
@@ -3954,9 +8120,17 @@
"name": "oncoming fire engine",
"shortname": ":fire_engine_oncoming:",
"category": "travel_places",
- "aliases": [":oncoming_fire_engine:"],
- "aliases_ascii": [],
- "keywords": ["transportation", "vehicle", "fighter", "truck", "emergency"]
+ "aliases": [
+ ":oncoming_fire_engine:"
+ ],
+ "aliases_ascii": [],
+ "keywords": [
+ "transportation",
+ "vehicle",
+ "fighter",
+ "truck",
+ "emergency"
+ ]
},
"fireworks": {
"unicode": "1F386",
@@ -3966,7 +8140,22 @@
"category": "objects",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["carnival", "congratulations", "festival", "photo", "fireworks", "independence", "celebration", "explosion", "july", "4th", "rocket", "sky", "idea", "excitement"],
+ "keywords": [
+ "carnival",
+ "congratulations",
+ "festival",
+ "photo",
+ "fireworks",
+ "independence",
+ "celebration",
+ "explosion",
+ "july",
+ "4th",
+ "rocket",
+ "sky",
+ "idea",
+ "excitement"
+ ],
"moji": "🎆"
},
"first_quarter_moon": {
@@ -3977,7 +8166,16 @@
"category": "nature",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["nature", "moon", "quarter", "first", "sky", "night", "cheese", "phase"],
+ "keywords": [
+ "nature",
+ "moon",
+ "quarter",
+ "first",
+ "sky",
+ "night",
+ "cheese",
+ "phase"
+ ],
"moji": "🌓"
},
"first_quarter_moon_with_face": {
@@ -3988,7 +8186,18 @@
"category": "nature",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["nature", "moon", "first", "quarter", "anthropomorphic", "face", "sky", "night", "cheese", "phase"],
+ "keywords": [
+ "nature",
+ "moon",
+ "first",
+ "quarter",
+ "anthropomorphic",
+ "face",
+ "sky",
+ "night",
+ "cheese",
+ "phase"
+ ],
"moji": "🌛"
},
"fish": {
@@ -3999,7 +8208,11 @@
"category": "nature",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["animal", "food", "nature"],
+ "keywords": [
+ "animal",
+ "food",
+ "nature"
+ ],
"moji": "🐟"
},
"fish_cake": {
@@ -4010,7 +8223,16 @@
"category": "objects",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["food", "fish", "cake", "kamboko", "swirl", "ramen", "noodles", "naruto"],
+ "keywords": [
+ "food",
+ "fish",
+ "cake",
+ "kamboko",
+ "swirl",
+ "ramen",
+ "noodles",
+ "naruto"
+ ],
"moji": "🍥"
},
"fishing_pole_and_fish": {
@@ -4021,7 +8243,13 @@
"category": "objects",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["food", "hobby", "fish", "fishing", "pole"],
+ "keywords": [
+ "food",
+ "hobby",
+ "fish",
+ "fishing",
+ "pole"
+ ],
"moji": "🎣"
},
"fist": {
@@ -4032,19 +8260,99 @@
"category": "emoticons",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["fingers", "grasp", "hand"],
+ "keywords": [
+ "fingers",
+ "grasp",
+ "hand"
+ ],
"moji": "✊"
},
+ "fist_tone1": {
+ "unicode": "270A-1F3FB",
+ "unicode_alternates": "",
+ "name": "raised fist tone 1",
+ "shortname": ":fist_tone1:",
+ "category": "people",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": [
+ "fingers",
+ "grasp",
+ "hand"
+ ]
+ },
+ "fist_tone2": {
+ "unicode": "270A-1F3FC",
+ "unicode_alternates": "",
+ "name": "raised fist tone 2",
+ "shortname": ":fist_tone2:",
+ "category": "people",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": [
+ "fingers",
+ "grasp",
+ "hand"
+ ]
+ },
+ "fist_tone3": {
+ "unicode": "270A-1F3FD",
+ "unicode_alternates": "",
+ "name": "raised fist tone 3",
+ "shortname": ":fist_tone3:",
+ "category": "people",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": [
+ "fingers",
+ "grasp",
+ "hand"
+ ]
+ },
+ "fist_tone4": {
+ "unicode": "270A-1F3FE",
+ "unicode_alternates": "",
+ "name": "raised fist tone 4",
+ "shortname": ":fist_tone4:",
+ "category": "people",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": [
+ "fingers",
+ "grasp",
+ "hand"
+ ]
+ },
+ "fist_tone5": {
+ "unicode": "270A-1F3FF",
+ "unicode_alternates": "",
+ "name": "raised fist tone 5",
+ "shortname": ":fist_tone5:",
+ "category": "people",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": [
+ "fingers",
+ "grasp",
+ "hand"
+ ]
+ },
"five": {
"moji": "5️⃣",
"unicode": "0035-20E3",
- "unicode_alternates": ["0035-FE0F-20E3"],
+ "unicode_alternates": [
+ "0035-FE0F-20E3"
+ ],
"name": "digit five",
"shortname": ":five:",
"category": "other",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["blue-square", "numbers", "prime"]
+ "keywords": [
+ "blue-square",
+ "numbers",
+ "prime"
+ ]
},
"flag_ac": {
"unicode": "1F1E6-1F1E8",
@@ -4052,9 +8360,15 @@
"name": "ascension",
"shortname": ":flag_ac:",
"category": "flags",
- "aliases": [":ac:"],
+ "aliases": [
+ ":ac:"
+ ],
"aliases_ascii": [],
- "keywords": ["country", "nation", "ac"]
+ "keywords": [
+ "country",
+ "nation",
+ "ac"
+ ]
},
"flag_ad": {
"unicode": "1F1E6-1F1E9",
@@ -4062,9 +8376,15 @@
"name": "andorra",
"shortname": ":flag_ad:",
"category": "flags",
- "aliases": [":ad:"],
+ "aliases": [
+ ":ad:"
+ ],
"aliases_ascii": [],
- "keywords": ["country", "nation", "ad"]
+ "keywords": [
+ "country",
+ "nation",
+ "ad"
+ ]
},
"flag_ae": {
"unicode": "1F1E6-1F1EA",
@@ -4072,9 +8392,15 @@
"name": "the united arab emirates",
"shortname": ":flag_ae:",
"category": "flags",
- "aliases": [":ae:"],
+ "aliases": [
+ ":ae:"
+ ],
"aliases_ascii": [],
- "keywords": ["country", "nation", "ae"]
+ "keywords": [
+ "country",
+ "nation",
+ "ae"
+ ]
},
"flag_af": {
"unicode": "1F1E6-1F1EB",
@@ -4082,9 +8408,16 @@
"name": "afghanistan",
"shortname": ":flag_af:",
"category": "flags",
- "aliases": [":af:"],
+ "aliases": [
+ ":af:"
+ ],
"aliases_ascii": [],
- "keywords": ["country", "nation", "afghanestan", "af"]
+ "keywords": [
+ "country",
+ "nation",
+ "afghanestan",
+ "af"
+ ]
},
"flag_ag": {
"unicode": "1F1E6-1F1EC",
@@ -4092,9 +8425,15 @@
"name": "antigua and barbuda",
"shortname": ":flag_ag:",
"category": "flags",
- "aliases": [":ag:"],
+ "aliases": [
+ ":ag:"
+ ],
"aliases_ascii": [],
- "keywords": ["country", "nation", "ag"]
+ "keywords": [
+ "country",
+ "nation",
+ "ag"
+ ]
},
"flag_ai": {
"unicode": "1F1E6-1F1EE",
@@ -4102,9 +8441,15 @@
"name": "anguilla",
"shortname": ":flag_ai:",
"category": "flags",
- "aliases": [":ai:"],
+ "aliases": [
+ ":ai:"
+ ],
"aliases_ascii": [],
- "keywords": ["country", "nation", "ai"]
+ "keywords": [
+ "country",
+ "nation",
+ "ai"
+ ]
},
"flag_al": {
"unicode": "1F1E6-1F1F1",
@@ -4112,9 +8457,16 @@
"name": "albania",
"shortname": ":flag_al:",
"category": "flags",
- "aliases": [":al:"],
+ "aliases": [
+ ":al:"
+ ],
"aliases_ascii": [],
- "keywords": ["country", "nation", "shqiperia", "al"]
+ "keywords": [
+ "country",
+ "nation",
+ "shqiperia",
+ "al"
+ ]
},
"flag_am": {
"unicode": "1F1E6-1F1F2",
@@ -4122,9 +8474,16 @@
"name": "armenia",
"shortname": ":flag_am:",
"category": "flags",
- "aliases": [":am:"],
+ "aliases": [
+ ":am:"
+ ],
"aliases_ascii": [],
- "keywords": ["country", "nation", "hayastan", "am"]
+ "keywords": [
+ "country",
+ "nation",
+ "hayastan",
+ "am"
+ ]
},
"flag_ao": {
"unicode": "1F1E6-1F1F4",
@@ -4132,9 +8491,27 @@
"name": "angola",
"shortname": ":flag_ao:",
"category": "flags",
- "aliases": [":ao:"],
+ "aliases": [
+ ":ao:"
+ ],
+ "aliases_ascii": [],
+ "keywords": [
+ "country",
+ "nation",
+ "ao"
+ ]
+ },
+ "flag_aq": {
+ "unicode": "1F1E6-1F1F6",
+ "unicode_alternates": "",
+ "name": "antarctica",
+ "shortname": ":flag_aq:",
+ "category": "flags",
+ "aliases": [
+ ":aq:"
+ ],
"aliases_ascii": [],
- "keywords": ["country", "nation", "ao"]
+ "keywords": []
},
"flag_ar": {
"unicode": "1F1E6-1F1F7",
@@ -4142,9 +8519,27 @@
"name": "argentina",
"shortname": ":flag_ar:",
"category": "flags",
- "aliases": [":ar:"],
+ "aliases": [
+ ":ar:"
+ ],
+ "aliases_ascii": [],
+ "keywords": [
+ "country",
+ "nation",
+ "ar"
+ ]
+ },
+ "flag_as": {
+ "unicode": "1F1E6-1F1F8",
+ "unicode_alternates": "",
+ "name": "american samoa",
+ "shortname": ":flag_as:",
+ "category": "flags",
+ "aliases": [
+ ":as:"
+ ],
"aliases_ascii": [],
- "keywords": ["country", "nation", "ar"]
+ "keywords": []
},
"flag_at": {
"unicode": "1F1E6-1F1F9",
@@ -4152,9 +8547,17 @@
"name": "austria",
"shortname": ":flag_at:",
"category": "flags",
- "aliases": [":at:"],
- "aliases_ascii": [],
- "keywords": ["country", "nation", "&ouml;sterreich", "osterreich", "at"]
+ "aliases": [
+ ":at:"
+ ],
+ "aliases_ascii": [],
+ "keywords": [
+ "country",
+ "nation",
+ "&ouml;sterreich",
+ "osterreich",
+ "at"
+ ]
},
"flag_au": {
"unicode": "1F1E6-1F1FA",
@@ -4162,9 +8565,15 @@
"name": "australia",
"shortname": ":flag_au:",
"category": "flags",
- "aliases": [":au:"],
+ "aliases": [
+ ":au:"
+ ],
"aliases_ascii": [],
- "keywords": ["country", "nation", "au"]
+ "keywords": [
+ "country",
+ "nation",
+ "au"
+ ]
},
"flag_aw": {
"unicode": "1F1E6-1F1FC",
@@ -4172,9 +8581,27 @@
"name": "aruba",
"shortname": ":flag_aw:",
"category": "flags",
- "aliases": [":aw:"],
+ "aliases": [
+ ":aw:"
+ ],
+ "aliases_ascii": [],
+ "keywords": [
+ "country",
+ "nation",
+ "aw"
+ ]
+ },
+ "flag_ax": {
+ "unicode": "1F1E6-1F1FD",
+ "unicode_alternates": "",
+ "name": "åland islands",
+ "shortname": ":flag_ax:",
+ "category": "flags",
+ "aliases": [
+ ":ax:"
+ ],
"aliases_ascii": [],
- "keywords": ["country", "nation", "aw"]
+ "keywords": []
},
"flag_az": {
"unicode": "1F1E6-1F1FF",
@@ -4182,9 +8609,16 @@
"name": "azerbaijan",
"shortname": ":flag_az:",
"category": "flags",
- "aliases": [":az:"],
+ "aliases": [
+ ":az:"
+ ],
"aliases_ascii": [],
- "keywords": ["country", "nation", "azarbaycan", "az"]
+ "keywords": [
+ "country",
+ "nation",
+ "azarbaycan",
+ "az"
+ ]
},
"flag_ba": {
"unicode": "1F1E7-1F1E6",
@@ -4192,9 +8626,16 @@
"name": "bosnia and herzegovina",
"shortname": ":flag_ba:",
"category": "flags",
- "aliases": [":ba:"],
+ "aliases": [
+ ":ba:"
+ ],
"aliases_ascii": [],
- "keywords": ["country", "nation", "bosna i hercegovina", "ba"]
+ "keywords": [
+ "country",
+ "nation",
+ "bosna i hercegovina",
+ "ba"
+ ]
},
"flag_bb": {
"unicode": "1F1E7-1F1E7",
@@ -4202,9 +8643,15 @@
"name": "barbados",
"shortname": ":flag_bb:",
"category": "flags",
- "aliases": [":bb:"],
+ "aliases": [
+ ":bb:"
+ ],
"aliases_ascii": [],
- "keywords": ["country", "nation", "bb"]
+ "keywords": [
+ "country",
+ "nation",
+ "bb"
+ ]
},
"flag_bd": {
"unicode": "1F1E7-1F1E9",
@@ -4212,9 +8659,15 @@
"name": "bangladesh",
"shortname": ":flag_bd:",
"category": "flags",
- "aliases": [":bd:"],
+ "aliases": [
+ ":bd:"
+ ],
"aliases_ascii": [],
- "keywords": ["country", "nation", "bd"]
+ "keywords": [
+ "country",
+ "nation",
+ "bd"
+ ]
},
"flag_be": {
"unicode": "1F1E7-1F1EA",
@@ -4222,9 +8675,17 @@
"name": "belgium",
"shortname": ":flag_be:",
"category": "flags",
- "aliases": [":be:"],
- "aliases_ascii": [],
- "keywords": ["country", "nation", "belgique", "belgie", "be"]
+ "aliases": [
+ ":be:"
+ ],
+ "aliases_ascii": [],
+ "keywords": [
+ "country",
+ "nation",
+ "belgique",
+ "belgie",
+ "be"
+ ]
},
"flag_bf": {
"unicode": "1F1E7-1F1EB",
@@ -4232,9 +8693,15 @@
"name": "burkina faso",
"shortname": ":flag_bf:",
"category": "flags",
- "aliases": [":bf:"],
+ "aliases": [
+ ":bf:"
+ ],
"aliases_ascii": [],
- "keywords": ["country", "nation", "bf"]
+ "keywords": [
+ "country",
+ "nation",
+ "bf"
+ ]
},
"flag_bg": {
"unicode": "1F1E7-1F1EC",
@@ -4242,9 +8709,15 @@
"name": "bulgaria",
"shortname": ":flag_bg:",
"category": "flags",
- "aliases": [":bg:"],
+ "aliases": [
+ ":bg:"
+ ],
"aliases_ascii": [],
- "keywords": ["country", "nation", "bg"]
+ "keywords": [
+ "country",
+ "nation",
+ "bg"
+ ]
},
"flag_bh": {
"unicode": "1F1E7-1F1ED",
@@ -4252,9 +8725,16 @@
"name": "bahrain",
"shortname": ":flag_bh:",
"category": "flags",
- "aliases": [":bh:"],
+ "aliases": [
+ ":bh:"
+ ],
"aliases_ascii": [],
- "keywords": ["country", "nation", "al bahrayn", "bh"]
+ "keywords": [
+ "country",
+ "nation",
+ "al bahrayn",
+ "bh"
+ ]
},
"flag_bi": {
"unicode": "1F1E7-1F1EE",
@@ -4262,9 +8742,15 @@
"name": "burundi",
"shortname": ":flag_bi:",
"category": "flags",
- "aliases": [":bi:"],
+ "aliases": [
+ ":bi:"
+ ],
"aliases_ascii": [],
- "keywords": ["country", "nation", "bi"]
+ "keywords": [
+ "country",
+ "nation",
+ "bi"
+ ]
},
"flag_bj": {
"unicode": "1F1E7-1F1EF",
@@ -4272,9 +8758,27 @@
"name": "benin",
"shortname": ":flag_bj:",
"category": "flags",
- "aliases": [":bj:"],
+ "aliases": [
+ ":bj:"
+ ],
+ "aliases_ascii": [],
+ "keywords": [
+ "country",
+ "nation",
+ "bj"
+ ]
+ },
+ "flag_bl": {
+ "unicode": "1F1E7-1F1F1",
+ "unicode_alternates": "",
+ "name": "saint barthélemy",
+ "shortname": ":flag_bl:",
+ "category": "flags",
+ "aliases": [
+ ":bl:"
+ ],
"aliases_ascii": [],
- "keywords": ["country", "nation", "bj"]
+ "keywords": []
},
"flag_black": {
"unicode": "1F3F4",
@@ -4282,9 +8786,14 @@
"name": "waving black flag",
"shortname": ":flag_black:",
"category": "objects_symbols",
- "aliases": [":waving_black_flag:"],
+ "aliases": [
+ ":waving_black_flag:"
+ ],
"aliases_ascii": [],
- "keywords": ["symbol", "signal"]
+ "keywords": [
+ "symbol",
+ "signal"
+ ]
},
"flag_bm": {
"unicode": "1F1E7-1F1F2",
@@ -4292,9 +8801,15 @@
"name": "bermuda",
"shortname": ":flag_bm:",
"category": "flags",
- "aliases": [":bm:"],
+ "aliases": [
+ ":bm:"
+ ],
"aliases_ascii": [],
- "keywords": ["country", "nation", "bm"]
+ "keywords": [
+ "country",
+ "nation",
+ "bm"
+ ]
},
"flag_bn": {
"unicode": "1F1E7-1F1F3",
@@ -4302,9 +8817,15 @@
"name": "brunei",
"shortname": ":flag_bn:",
"category": "flags",
- "aliases": [":bn:"],
+ "aliases": [
+ ":bn:"
+ ],
"aliases_ascii": [],
- "keywords": ["country", "nation", "bn"]
+ "keywords": [
+ "country",
+ "nation",
+ "bn"
+ ]
},
"flag_bo": {
"unicode": "1F1E7-1F1F4",
@@ -4312,9 +8833,27 @@
"name": "bolivia",
"shortname": ":flag_bo:",
"category": "flags",
- "aliases": [":bo:"],
+ "aliases": [
+ ":bo:"
+ ],
+ "aliases_ascii": [],
+ "keywords": [
+ "country",
+ "nation",
+ "bo"
+ ]
+ },
+ "flag_bq": {
+ "unicode": "1F1E7-1F1F6",
+ "unicode_alternates": "",
+ "name": "caribbean netherlands",
+ "shortname": ":flag_bq:",
+ "category": "flags",
+ "aliases": [
+ ":bq:"
+ ],
"aliases_ascii": [],
- "keywords": ["country", "nation", "bo"]
+ "keywords": []
},
"flag_br": {
"unicode": "1F1E7-1F1F7",
@@ -4322,9 +8861,16 @@
"name": "brazil",
"shortname": ":flag_br:",
"category": "flags",
- "aliases": [":br:"],
+ "aliases": [
+ ":br:"
+ ],
"aliases_ascii": [],
- "keywords": ["country", "nation", "brasil", "br"]
+ "keywords": [
+ "country",
+ "nation",
+ "brasil",
+ "br"
+ ]
},
"flag_bs": {
"unicode": "1F1E7-1F1F8",
@@ -4332,9 +8878,15 @@
"name": "the bahamas",
"shortname": ":flag_bs:",
"category": "flags",
- "aliases": [":bs:"],
+ "aliases": [
+ ":bs:"
+ ],
"aliases_ascii": [],
- "keywords": ["country", "nation", "bs"]
+ "keywords": [
+ "country",
+ "nation",
+ "bs"
+ ]
},
"flag_bt": {
"unicode": "1F1E7-1F1F9",
@@ -4342,9 +8894,27 @@
"name": "bhutan",
"shortname": ":flag_bt:",
"category": "flags",
- "aliases": [":bt:"],
+ "aliases": [
+ ":bt:"
+ ],
+ "aliases_ascii": [],
+ "keywords": [
+ "country",
+ "nation",
+ "bt"
+ ]
+ },
+ "flag_bv": {
+ "unicode": "1F1E7-1F1FB",
+ "unicode_alternates": "",
+ "name": "bouvet island",
+ "shortname": ":flag_bv:",
+ "category": "flags",
+ "aliases": [
+ ":bv:"
+ ],
"aliases_ascii": [],
- "keywords": ["country", "nation", "bt"]
+ "keywords": []
},
"flag_bw": {
"unicode": "1F1E7-1F1FC",
@@ -4352,9 +8922,15 @@
"name": "botswana",
"shortname": ":flag_bw:",
"category": "flags",
- "aliases": [":bw:"],
+ "aliases": [
+ ":bw:"
+ ],
"aliases_ascii": [],
- "keywords": ["country", "nation", "bw"]
+ "keywords": [
+ "country",
+ "nation",
+ "bw"
+ ]
},
"flag_by": {
"unicode": "1F1E7-1F1FE",
@@ -4362,9 +8938,16 @@
"name": "belarus",
"shortname": ":flag_by:",
"category": "flags",
- "aliases": [":by:"],
+ "aliases": [
+ ":by:"
+ ],
"aliases_ascii": [],
- "keywords": ["country", "nation", "byelarus", "by"]
+ "keywords": [
+ "country",
+ "nation",
+ "byelarus",
+ "by"
+ ]
},
"flag_bz": {
"unicode": "1F1E7-1F1FF",
@@ -4372,9 +8955,15 @@
"name": "belize",
"shortname": ":flag_bz:",
"category": "flags",
- "aliases": [":bz:"],
+ "aliases": [
+ ":bz:"
+ ],
"aliases_ascii": [],
- "keywords": ["country", "nation", "bz"]
+ "keywords": [
+ "country",
+ "nation",
+ "bz"
+ ]
},
"flag_ca": {
"unicode": "1F1E8-1F1E6",
@@ -4382,9 +8971,27 @@
"name": "canada",
"shortname": ":flag_ca:",
"category": "flags",
- "aliases": [":ca:"],
+ "aliases": [
+ ":ca:"
+ ],
+ "aliases_ascii": [],
+ "keywords": [
+ "country",
+ "nation",
+ "ca"
+ ]
+ },
+ "flag_cc": {
+ "unicode": "1F1E8-1F1E8",
+ "unicode_alternates": "",
+ "name": "cocos (keeling) islands",
+ "shortname": ":flag_cc:",
+ "category": "flags",
+ "aliases": [
+ ":cc:"
+ ],
"aliases_ascii": [],
- "keywords": ["country", "nation", "ca"]
+ "keywords": []
},
"flag_cd": {
"unicode": "1F1E8-1F1E9",
@@ -4392,9 +8999,17 @@
"name": "the democratic republic of the congo",
"shortname": ":flag_cd:",
"category": "flags",
- "aliases": [":congo:"],
- "aliases_ascii": [],
- "keywords": ["country", "nation", "r&eacute;publique d&eacute;mocratique du congo", "republique democratique du congo", "cd"]
+ "aliases": [
+ ":congo:"
+ ],
+ "aliases_ascii": [],
+ "keywords": [
+ "country",
+ "nation",
+ "r&eacute;publique d&eacute;mocratique du congo",
+ "republique democratique du congo",
+ "cd"
+ ]
},
"flag_cf": {
"unicode": "1F1E8-1F1EB",
@@ -4402,9 +9017,15 @@
"name": "central african republic",
"shortname": ":flag_cf:",
"category": "flags",
- "aliases": [":cf:"],
+ "aliases": [
+ ":cf:"
+ ],
"aliases_ascii": [],
- "keywords": ["country", "nation", "cf"]
+ "keywords": [
+ "country",
+ "nation",
+ "cf"
+ ]
},
"flag_cg": {
"unicode": "1F1E8-1F1EC",
@@ -4412,9 +9033,15 @@
"name": "the republic of the congo",
"shortname": ":flag_cg:",
"category": "flags",
- "aliases": [":cg:"],
+ "aliases": [
+ ":cg:"
+ ],
"aliases_ascii": [],
- "keywords": ["country", "nation", "cg"]
+ "keywords": [
+ "country",
+ "nation",
+ "cg"
+ ]
},
"flag_ch": {
"unicode": "1F1E8-1F1ED",
@@ -4422,9 +9049,15 @@
"name": "switzerland",
"shortname": ":flag_ch:",
"category": "flags",
- "aliases": [":ch:"],
+ "aliases": [
+ ":ch:"
+ ],
"aliases_ascii": [],
- "keywords": ["country", "nation", "swiss"]
+ "keywords": [
+ "country",
+ "nation",
+ "swiss"
+ ]
},
"flag_ci": {
"unicode": "1F1E8-1F1EE",
@@ -4432,9 +9065,27 @@
"name": "cote d'ivoire",
"shortname": ":flag_ci:",
"category": "flags",
- "aliases": [":ci:"],
+ "aliases": [
+ ":ci:"
+ ],
+ "aliases_ascii": [],
+ "keywords": [
+ "country",
+ "nation",
+ "ci"
+ ]
+ },
+ "flag_ck": {
+ "unicode": "1F1E8-1F1F0",
+ "unicode_alternates": "",
+ "name": "cook islands",
+ "shortname": ":flag_ck:",
+ "category": "flags",
+ "aliases": [
+ ":ck:"
+ ],
"aliases_ascii": [],
- "keywords": ["country", "nation", "ci"]
+ "keywords": []
},
"flag_cl": {
"unicode": "1F1E8-1F1F1",
@@ -4442,9 +9093,15 @@
"name": "chile",
"shortname": ":flag_cl:",
"category": "flags",
- "aliases": [":chile:"],
+ "aliases": [
+ ":chile:"
+ ],
"aliases_ascii": [],
- "keywords": ["country", "nation", "cl"]
+ "keywords": [
+ "country",
+ "nation",
+ "cl"
+ ]
},
"flag_cm": {
"unicode": "1F1E8-1F1F2",
@@ -4452,9 +9109,15 @@
"name": "cameroon",
"shortname": ":flag_cm:",
"category": "flags",
- "aliases": [":cm:"],
+ "aliases": [
+ ":cm:"
+ ],
"aliases_ascii": [],
- "keywords": ["country", "nation", "cm"]
+ "keywords": [
+ "country",
+ "nation",
+ "cm"
+ ]
},
"flag_cn": {
"unicode": "1F1E8-1F1F3",
@@ -4462,9 +9125,18 @@
"name": "china",
"shortname": ":flag_cn:",
"category": "flags",
- "aliases": [":cn:"],
- "aliases_ascii": [],
- "keywords": ["chinese", "prc", "zhong guo", "country", "nation", "cn"]
+ "aliases": [
+ ":cn:"
+ ],
+ "aliases_ascii": [],
+ "keywords": [
+ "chinese",
+ "prc",
+ "zhong guo",
+ "country",
+ "nation",
+ "cn"
+ ]
},
"flag_co": {
"unicode": "1F1E8-1F1F4",
@@ -4472,9 +9144,27 @@
"name": "colombia",
"shortname": ":flag_co:",
"category": "flags",
- "aliases": [":co:"],
+ "aliases": [
+ ":co:"
+ ],
+ "aliases_ascii": [],
+ "keywords": [
+ "country",
+ "nation",
+ "co"
+ ]
+ },
+ "flag_cp": {
+ "unicode": "1F1E8-1F1F5",
+ "unicode_alternates": "",
+ "name": "clipperton island",
+ "shortname": ":flag_cp:",
+ "category": "flags",
+ "aliases": [
+ ":cp:"
+ ],
"aliases_ascii": [],
- "keywords": ["country", "nation", "co"]
+ "keywords": []
},
"flag_cr": {
"unicode": "1F1E8-1F1F7",
@@ -4482,9 +9172,15 @@
"name": "costa rica",
"shortname": ":flag_cr:",
"category": "flags",
- "aliases": [":cr:"],
+ "aliases": [
+ ":cr:"
+ ],
"aliases_ascii": [],
- "keywords": ["country", "nation", "cr"]
+ "keywords": [
+ "country",
+ "nation",
+ "cr"
+ ]
},
"flag_cu": {
"unicode": "1F1E8-1F1FA",
@@ -4492,9 +9188,15 @@
"name": "cuba",
"shortname": ":flag_cu:",
"category": "flags",
- "aliases": [":cu:"],
+ "aliases": [
+ ":cu:"
+ ],
"aliases_ascii": [],
- "keywords": ["country", "nation", "cu"]
+ "keywords": [
+ "country",
+ "nation",
+ "cu"
+ ]
},
"flag_cv": {
"unicode": "1F1E8-1F1FB",
@@ -4502,9 +9204,40 @@
"name": "cape verde",
"shortname": ":flag_cv:",
"category": "flags",
- "aliases": [":cv:"],
+ "aliases": [
+ ":cv:"
+ ],
+ "aliases_ascii": [],
+ "keywords": [
+ "country",
+ "nation",
+ "cabo verde",
+ "cv"
+ ]
+ },
+ "flag_cw": {
+ "unicode": "1F1E8-1F1FC",
+ "unicode_alternates": "",
+ "name": "curaçao",
+ "shortname": ":flag_cw:",
+ "category": "flags",
+ "aliases": [
+ ":cw:"
+ ],
+ "aliases_ascii": [],
+ "keywords": []
+ },
+ "flag_cx": {
+ "unicode": "1F1E8-1F1FD",
+ "unicode_alternates": "",
+ "name": "christmas island",
+ "shortname": ":flag_cx:",
+ "category": "flags",
+ "aliases": [
+ ":cx:"
+ ],
"aliases_ascii": [],
- "keywords": ["country", "nation", "cabo verde", "cv"]
+ "keywords": []
},
"flag_cy": {
"unicode": "1F1E8-1F1FE",
@@ -4512,9 +9245,17 @@
"name": "cyprus",
"shortname": ":flag_cy:",
"category": "flags",
- "aliases": [":cy:"],
- "aliases_ascii": [],
- "keywords": ["country", "nation", "kibris", "kypros", "cy"]
+ "aliases": [
+ ":cy:"
+ ],
+ "aliases_ascii": [],
+ "keywords": [
+ "country",
+ "nation",
+ "kibris",
+ "kypros",
+ "cy"
+ ]
},
"flag_cz": {
"unicode": "1F1E8-1F1FF",
@@ -4522,9 +9263,16 @@
"name": "the czech republic",
"shortname": ":flag_cz:",
"category": "flags",
- "aliases": [":cz:"],
+ "aliases": [
+ ":cz:"
+ ],
"aliases_ascii": [],
- "keywords": ["country", "nation", "ceska republika", "cz"]
+ "keywords": [
+ "country",
+ "nation",
+ "ceska republika",
+ "cz"
+ ]
},
"flag_de": {
"unicode": "1F1E9-1F1EA",
@@ -4532,9 +9280,29 @@
"name": "germany",
"shortname": ":flag_de:",
"category": "flags",
- "aliases": [":de:"],
+ "aliases": [
+ ":de:"
+ ],
+ "aliases_ascii": [],
+ "keywords": [
+ "german",
+ "nation",
+ "deutschland",
+ "country",
+ "de"
+ ]
+ },
+ "flag_dg": {
+ "unicode": "1F1E9-1F1EC",
+ "unicode_alternates": "",
+ "name": "diego garcia",
+ "shortname": ":flag_dg:",
+ "category": "flags",
+ "aliases": [
+ ":dg:"
+ ],
"aliases_ascii": [],
- "keywords": ["german", "nation", "deutschland", "country", "de"]
+ "keywords": []
},
"flag_dj": {
"unicode": "1F1E9-1F1EF",
@@ -4542,9 +9310,15 @@
"name": "djibouti",
"shortname": ":flag_dj:",
"category": "flags",
- "aliases": [":dj:"],
+ "aliases": [
+ ":dj:"
+ ],
"aliases_ascii": [],
- "keywords": ["country", "nation", "dj"]
+ "keywords": [
+ "country",
+ "nation",
+ "dj"
+ ]
},
"flag_dk": {
"unicode": "1F1E9-1F1F0",
@@ -4552,9 +9326,16 @@
"name": "denmark",
"shortname": ":flag_dk:",
"category": "flags",
- "aliases": [":dk:"],
+ "aliases": [
+ ":dk:"
+ ],
"aliases_ascii": [],
- "keywords": ["country", "nation", "danmark", "dk"]
+ "keywords": [
+ "country",
+ "nation",
+ "danmark",
+ "dk"
+ ]
},
"flag_dm": {
"unicode": "1F1E9-1F1F2",
@@ -4562,9 +9343,15 @@
"name": "dominica",
"shortname": ":flag_dm:",
"category": "flags",
- "aliases": [":dm:"],
+ "aliases": [
+ ":dm:"
+ ],
"aliases_ascii": [],
- "keywords": ["country", "nation", "dm"]
+ "keywords": [
+ "country",
+ "nation",
+ "dm"
+ ]
},
"flag_do": {
"unicode": "1F1E9-1F1F4",
@@ -4572,9 +9359,15 @@
"name": "the dominican republic",
"shortname": ":flag_do:",
"category": "flags",
- "aliases": [":do:"],
+ "aliases": [
+ ":do:"
+ ],
"aliases_ascii": [],
- "keywords": ["country", "nation", "do"]
+ "keywords": [
+ "country",
+ "nation",
+ "do"
+ ]
},
"flag_dz": {
"unicode": "1F1E9-1F1FF",
@@ -4582,9 +9375,29 @@
"name": "algeria",
"shortname": ":flag_dz:",
"category": "flags",
- "aliases": [":dz:"],
+ "aliases": [
+ ":dz:"
+ ],
+ "aliases_ascii": [],
+ "keywords": [
+ "country",
+ "nation",
+ "al jaza'ir",
+ "al jazair",
+ "dz"
+ ]
+ },
+ "flag_ea": {
+ "unicode": "1F1EA-1F1E6",
+ "unicode_alternates": "",
+ "name": "ceuta, melilla",
+ "shortname": ":flag_ea:",
+ "category": "flags",
+ "aliases": [
+ ":ea:"
+ ],
"aliases_ascii": [],
- "keywords": ["country", "nation", "al jaza'ir", "al jazair", "dz"]
+ "keywords": []
},
"flag_ec": {
"unicode": "1F1EA-1F1E8",
@@ -4592,9 +9405,15 @@
"name": "ecuador",
"shortname": ":flag_ec:",
"category": "flags",
- "aliases": [":ec:"],
+ "aliases": [
+ ":ec:"
+ ],
"aliases_ascii": [],
- "keywords": ["country", "nation", "ec"]
+ "keywords": [
+ "country",
+ "nation",
+ "ec"
+ ]
},
"flag_ee": {
"unicode": "1F1EA-1F1EA",
@@ -4602,9 +9421,16 @@
"name": "estonia",
"shortname": ":flag_ee:",
"category": "flags",
- "aliases": [":ee:"],
+ "aliases": [
+ ":ee:"
+ ],
"aliases_ascii": [],
- "keywords": ["country", "nation", "eesti vabariik", "ee"]
+ "keywords": [
+ "country",
+ "nation",
+ "eesti vabariik",
+ "ee"
+ ]
},
"flag_eg": {
"unicode": "1F1EA-1F1EC",
@@ -4612,9 +9438,16 @@
"name": "egypt",
"shortname": ":flag_eg:",
"category": "flags",
- "aliases": [":eg:"],
+ "aliases": [
+ ":eg:"
+ ],
"aliases_ascii": [],
- "keywords": ["country", "nation", "misr", "eg"]
+ "keywords": [
+ "country",
+ "nation",
+ "misr",
+ "eg"
+ ]
},
"flag_eh": {
"unicode": "1F1EA-1F1ED",
@@ -4622,9 +9455,18 @@
"name": "western sahara",
"shortname": ":flag_eh:",
"category": "flags",
- "aliases": [":eh:"],
- "aliases_ascii": [],
- "keywords": ["country", "nation", "aṣ-Ṣaḥrā’ al-gharbīyah", "sahra", "gharbiyah", "eh"]
+ "aliases": [
+ ":eh:"
+ ],
+ "aliases_ascii": [],
+ "keywords": [
+ "country",
+ "nation",
+ "aṣ-Ṣaḥrā’ al-gharbīyah",
+ "sahra",
+ "gharbiyah",
+ "eh"
+ ]
},
"flag_er": {
"unicode": "1F1EA-1F1F7",
@@ -4632,9 +9474,16 @@
"name": "eritrea",
"shortname": ":flag_er:",
"category": "flags",
- "aliases": [":er:"],
+ "aliases": [
+ ":er:"
+ ],
"aliases_ascii": [],
- "keywords": ["country", "nation", "hagere ertra", "er"]
+ "keywords": [
+ "country",
+ "nation",
+ "hagere ertra",
+ "er"
+ ]
},
"flag_es": {
"unicode": "1F1EA-1F1F8",
@@ -4642,9 +9491,17 @@
"name": "spain",
"shortname": ":flag_es:",
"category": "flags",
- "aliases": [":es:"],
- "aliases_ascii": [],
- "keywords": ["nation", "espa&ntilde;a", "country", "espana", "es"]
+ "aliases": [
+ ":es:"
+ ],
+ "aliases_ascii": [],
+ "keywords": [
+ "nation",
+ "espa&ntilde;a",
+ "country",
+ "espana",
+ "es"
+ ]
},
"flag_et": {
"unicode": "1F1EA-1F1F9",
@@ -4652,9 +9509,29 @@
"name": "ethiopia",
"shortname": ":flag_et:",
"category": "flags",
- "aliases": [":et:"],
+ "aliases": [
+ ":et:"
+ ],
+ "aliases_ascii": [],
+ "keywords": [
+ "country",
+ "nation",
+ "ityop'iya",
+ "ityopiya",
+ "et"
+ ]
+ },
+ "flag_eu": {
+ "unicode": "1F1EA-1F1FA",
+ "unicode_alternates": "",
+ "name": "european union",
+ "shortname": ":flag_eu:",
+ "category": "flags",
+ "aliases": [
+ ":eu:"
+ ],
"aliases_ascii": [],
- "keywords": ["country", "nation", "ityop'iya", "ityopiya", "et"]
+ "keywords": []
},
"flag_fi": {
"unicode": "1F1EB-1F1EE",
@@ -4662,9 +9539,16 @@
"name": "finland",
"shortname": ":flag_fi:",
"category": "flags",
- "aliases": [":fi:"],
+ "aliases": [
+ ":fi:"
+ ],
"aliases_ascii": [],
- "keywords": ["country", "nation", "suomen tasavalta", "fi"]
+ "keywords": [
+ "country",
+ "nation",
+ "suomen tasavalta",
+ "fi"
+ ]
},
"flag_fj": {
"unicode": "1F1EB-1F1EF",
@@ -4672,9 +9556,15 @@
"name": "fiji",
"shortname": ":flag_fj:",
"category": "flags",
- "aliases": [":fj:"],
+ "aliases": [
+ ":fj:"
+ ],
"aliases_ascii": [],
- "keywords": ["country", "nation", "fj"]
+ "keywords": [
+ "country",
+ "nation",
+ "fj"
+ ]
},
"flag_fk": {
"unicode": "1F1EB-1F1F0",
@@ -4682,9 +9572,16 @@
"name": "falkland islands",
"shortname": ":flag_fk:",
"category": "flags",
- "aliases": [":fk:"],
+ "aliases": [
+ ":fk:"
+ ],
"aliases_ascii": [],
- "keywords": ["country", "nation", "islas malvinas", "fk"]
+ "keywords": [
+ "country",
+ "nation",
+ "islas malvinas",
+ "fk"
+ ]
},
"flag_fm": {
"unicode": "1F1EB-1F1F2",
@@ -4692,9 +9589,15 @@
"name": "micronesia",
"shortname": ":flag_fm:",
"category": "flags",
- "aliases": [":fm:"],
+ "aliases": [
+ ":fm:"
+ ],
"aliases_ascii": [],
- "keywords": ["country", "nation", "fm"]
+ "keywords": [
+ "country",
+ "nation",
+ "fm"
+ ]
},
"flag_fo": {
"unicode": "1F1EB-1F1F4",
@@ -4702,9 +9605,16 @@
"name": "faroe islands",
"shortname": ":flag_fo:",
"category": "flags",
- "aliases": [":fo:"],
+ "aliases": [
+ ":fo:"
+ ],
"aliases_ascii": [],
- "keywords": ["country", "nation", "foroyar", "fo"]
+ "keywords": [
+ "country",
+ "nation",
+ "foroyar",
+ "fo"
+ ]
},
"flag_fr": {
"unicode": "1F1EB-1F1F7",
@@ -4712,9 +9622,16 @@
"name": "france",
"shortname": ":flag_fr:",
"category": "flags",
- "aliases": [":fr:"],
+ "aliases": [
+ ":fr:"
+ ],
"aliases_ascii": [],
- "keywords": ["french", "nation", "country", "fr"]
+ "keywords": [
+ "french",
+ "nation",
+ "country",
+ "fr"
+ ]
},
"flag_ga": {
"unicode": "1F1EC-1F1E6",
@@ -4722,9 +9639,15 @@
"name": "gabon",
"shortname": ":flag_ga:",
"category": "flags",
- "aliases": [":ga:"],
+ "aliases": [
+ ":ga:"
+ ],
"aliases_ascii": [],
- "keywords": ["country", "nation", "ga"]
+ "keywords": [
+ "country",
+ "nation",
+ "ga"
+ ]
},
"flag_gb": {
"unicode": "1F1EC-1F1E7",
@@ -4732,9 +9655,19 @@
"name": "great britain",
"shortname": ":flag_gb:",
"category": "flags",
- "aliases": [":gb:"],
- "aliases_ascii": [],
- "keywords": ["UK", "gb", "britsh", "nation", "united kingdom", "england", "country"]
+ "aliases": [
+ ":gb:"
+ ],
+ "aliases_ascii": [],
+ "keywords": [
+ "UK",
+ "gb",
+ "britsh",
+ "nation",
+ "united kingdom",
+ "england",
+ "country"
+ ]
},
"flag_gd": {
"unicode": "1F1EC-1F1E9",
@@ -4742,9 +9675,15 @@
"name": "grenada",
"shortname": ":flag_gd:",
"category": "flags",
- "aliases": [":gd:"],
+ "aliases": [
+ ":gd:"
+ ],
"aliases_ascii": [],
- "keywords": ["country", "nation", "gd"]
+ "keywords": [
+ "country",
+ "nation",
+ "gd"
+ ]
},
"flag_ge": {
"unicode": "1F1EC-1F1EA",
@@ -4752,9 +9691,41 @@
"name": "georgia",
"shortname": ":flag_ge:",
"category": "flags",
- "aliases": [":ge:"],
+ "aliases": [
+ ":ge:"
+ ],
+ "aliases_ascii": [],
+ "keywords": [
+ "country",
+ "nation",
+ "sak'art'velo",
+ "sakartvelo",
+ "ge"
+ ]
+ },
+ "flag_gf": {
+ "unicode": "1F1EC-1F1EB",
+ "unicode_alternates": "",
+ "name": "french guiana",
+ "shortname": ":flag_gf:",
+ "category": "flags",
+ "aliases": [
+ ":gf:"
+ ],
+ "aliases_ascii": [],
+ "keywords": []
+ },
+ "flag_gg": {
+ "unicode": "1F1EC-1F1EC",
+ "unicode_alternates": "",
+ "name": "guernsey",
+ "shortname": ":flag_gg:",
+ "category": "flags",
+ "aliases": [
+ ":gg:"
+ ],
"aliases_ascii": [],
- "keywords": ["country", "nation", "sak'art'velo", "sakartvelo", "ge"]
+ "keywords": []
},
"flag_gh": {
"unicode": "1F1EC-1F1ED",
@@ -4762,9 +9733,15 @@
"name": "ghana",
"shortname": ":flag_gh:",
"category": "flags",
- "aliases": [":gh:"],
+ "aliases": [
+ ":gh:"
+ ],
"aliases_ascii": [],
- "keywords": ["country", "nation", "gh"]
+ "keywords": [
+ "country",
+ "nation",
+ "gh"
+ ]
},
"flag_gi": {
"unicode": "1F1EC-1F1EE",
@@ -4772,9 +9749,15 @@
"name": "gibraltar",
"shortname": ":flag_gi:",
"category": "flags",
- "aliases": [":gi:"],
+ "aliases": [
+ ":gi:"
+ ],
"aliases_ascii": [],
- "keywords": ["country", "nation", "gi"]
+ "keywords": [
+ "country",
+ "nation",
+ "gi"
+ ]
},
"flag_gl": {
"unicode": "1F1EC-1F1F1",
@@ -4782,9 +9765,16 @@
"name": "greenland",
"shortname": ":flag_gl:",
"category": "flags",
- "aliases": [":gl:"],
+ "aliases": [
+ ":gl:"
+ ],
"aliases_ascii": [],
- "keywords": ["country", "nation", "kalaallit nunaat", "gl"]
+ "keywords": [
+ "country",
+ "nation",
+ "kalaallit nunaat",
+ "gl"
+ ]
},
"flag_gm": {
"unicode": "1F1EC-1F1F2",
@@ -4792,9 +9782,15 @@
"name": "the gambia",
"shortname": ":flag_gm:",
"category": "flags",
- "aliases": [":gm:"],
+ "aliases": [
+ ":gm:"
+ ],
"aliases_ascii": [],
- "keywords": ["country", "nation", "gm"]
+ "keywords": [
+ "country",
+ "nation",
+ "gm"
+ ]
},
"flag_gn": {
"unicode": "1F1EC-1F1F3",
@@ -4802,9 +9798,28 @@
"name": "guinea",
"shortname": ":flag_gn:",
"category": "flags",
- "aliases": [":gn:"],
+ "aliases": [
+ ":gn:"
+ ],
+ "aliases_ascii": [],
+ "keywords": [
+ "country",
+ "nation",
+ "guinee",
+ "gn"
+ ]
+ },
+ "flag_gp": {
+ "unicode": "1F1EC-1F1F5",
+ "unicode_alternates": "",
+ "name": "guadeloupe",
+ "shortname": ":flag_gp:",
+ "category": "flags",
+ "aliases": [
+ ":gp:"
+ ],
"aliases_ascii": [],
- "keywords": ["country", "nation", "guinee", "gn"]
+ "keywords": []
},
"flag_gq": {
"unicode": "1F1EC-1F1F6",
@@ -4812,9 +9827,16 @@
"name": "equatorial guinea",
"shortname": ":flag_gq:",
"category": "flags",
- "aliases": [":gq:"],
+ "aliases": [
+ ":gq:"
+ ],
"aliases_ascii": [],
- "keywords": ["country", "nation", "guinea ecuatorial", "gq"]
+ "keywords": [
+ "country",
+ "nation",
+ "guinea ecuatorial",
+ "gq"
+ ]
},
"flag_gr": {
"unicode": "1F1EC-1F1F7",
@@ -4822,9 +9844,29 @@
"name": "greece",
"shortname": ":flag_gr:",
"category": "flags",
- "aliases": [":gr:"],
+ "aliases": [
+ ":gr:"
+ ],
+ "aliases_ascii": [],
+ "keywords": [
+ "country",
+ "nation",
+ "ellas",
+ "ellada",
+ "gr"
+ ]
+ },
+ "flag_gs": {
+ "unicode": "1F1EC-1F1F8",
+ "unicode_alternates": "",
+ "name": "south georgia",
+ "shortname": ":flag_gs:",
+ "category": "flags",
+ "aliases": [
+ ":gs:"
+ ],
"aliases_ascii": [],
- "keywords": ["country", "nation", "ellas", "ellada", "gr"]
+ "keywords": []
},
"flag_gt": {
"unicode": "1F1EC-1F1F9",
@@ -4832,9 +9874,15 @@
"name": "guatemala",
"shortname": ":flag_gt:",
"category": "flags",
- "aliases": [":gt:"],
+ "aliases": [
+ ":gt:"
+ ],
"aliases_ascii": [],
- "keywords": ["country", "nation", "gt"]
+ "keywords": [
+ "country",
+ "nation",
+ "gt"
+ ]
},
"flag_gu": {
"unicode": "1F1EC-1F1FA",
@@ -4842,9 +9890,15 @@
"name": "guam",
"shortname": ":flag_gu:",
"category": "flags",
- "aliases": [":gu:"],
+ "aliases": [
+ ":gu:"
+ ],
"aliases_ascii": [],
- "keywords": ["country", "nation", "gu"]
+ "keywords": [
+ "country",
+ "nation",
+ "gu"
+ ]
},
"flag_gw": {
"unicode": "1F1EC-1F1FC",
@@ -4852,9 +9906,17 @@
"name": "guinea-bissau",
"shortname": ":flag_gw:",
"category": "flags",
- "aliases": [":gw:"],
- "aliases_ascii": [],
- "keywords": ["country", "nation", "guine-bissau", "guine bissau", "gw"]
+ "aliases": [
+ ":gw:"
+ ],
+ "aliases_ascii": [],
+ "keywords": [
+ "country",
+ "nation",
+ "guine-bissau",
+ "guine bissau",
+ "gw"
+ ]
},
"flag_gy": {
"unicode": "1F1EC-1F1FE",
@@ -4862,9 +9924,15 @@
"name": "guyana",
"shortname": ":flag_gy:",
"category": "flags",
- "aliases": [":gy:"],
+ "aliases": [
+ ":gy:"
+ ],
"aliases_ascii": [],
- "keywords": ["country", "nation", "gy"]
+ "keywords": [
+ "country",
+ "nation",
+ "gy"
+ ]
},
"flag_hk": {
"unicode": "1F1ED-1F1F0",
@@ -4872,9 +9940,28 @@
"name": "hong kong",
"shortname": ":flag_hk:",
"category": "flags",
- "aliases": [":hk:"],
+ "aliases": [
+ ":hk:"
+ ],
+ "aliases_ascii": [],
+ "keywords": [
+ "country",
+ "nation",
+ "xianggang",
+ "hk"
+ ]
+ },
+ "flag_hm": {
+ "unicode": "1F1ED-1F1F2",
+ "unicode_alternates": "",
+ "name": "heard island and mcdonald islands",
+ "shortname": ":flag_hm:",
+ "category": "flags",
+ "aliases": [
+ ":hm:"
+ ],
"aliases_ascii": [],
- "keywords": ["country", "nation", "xianggang", "hk"]
+ "keywords": []
},
"flag_hn": {
"unicode": "1F1ED-1F1F3",
@@ -4882,9 +9969,15 @@
"name": "honduras",
"shortname": ":flag_hn:",
"category": "flags",
- "aliases": [":hn:"],
+ "aliases": [
+ ":hn:"
+ ],
"aliases_ascii": [],
- "keywords": ["country", "nation", "hn"]
+ "keywords": [
+ "country",
+ "nation",
+ "hn"
+ ]
},
"flag_hr": {
"unicode": "1F1ED-1F1F7",
@@ -4892,9 +9985,16 @@
"name": "croatia",
"shortname": ":flag_hr:",
"category": "flags",
- "aliases": [":hr:"],
+ "aliases": [
+ ":hr:"
+ ],
"aliases_ascii": [],
- "keywords": ["country", "nation", "hrvatska", "hr"]
+ "keywords": [
+ "country",
+ "nation",
+ "hrvatska",
+ "hr"
+ ]
},
"flag_ht": {
"unicode": "1F1ED-1F1F9",
@@ -4902,9 +10002,15 @@
"name": "haiti",
"shortname": ":flag_ht:",
"category": "flags",
- "aliases": [":ht:"],
+ "aliases": [
+ ":ht:"
+ ],
"aliases_ascii": [],
- "keywords": ["country", "nation", "ht"]
+ "keywords": [
+ "country",
+ "nation",
+ "ht"
+ ]
},
"flag_hu": {
"unicode": "1F1ED-1F1FA",
@@ -4912,9 +10018,28 @@
"name": "hungary",
"shortname": ":flag_hu:",
"category": "flags",
- "aliases": [":hu:"],
+ "aliases": [
+ ":hu:"
+ ],
+ "aliases_ascii": [],
+ "keywords": [
+ "country",
+ "nation",
+ "magyarorszag",
+ "hu"
+ ]
+ },
+ "flag_ic": {
+ "unicode": "1F1EE-1F1E8",
+ "unicode_alternates": "",
+ "name": "canary islands",
+ "shortname": ":flag_ic:",
+ "category": "flags",
+ "aliases": [
+ ":ic:"
+ ],
"aliases_ascii": [],
- "keywords": ["country", "nation", "magyarorszag", "hu"]
+ "keywords": []
},
"flag_id": {
"unicode": "1F1EE-1F1E9",
@@ -4922,9 +10047,15 @@
"name": "indonesia",
"shortname": ":flag_id:",
"category": "flags",
- "aliases": [":indonesia:"],
+ "aliases": [
+ ":indonesia:"
+ ],
"aliases_ascii": [],
- "keywords": ["country", "nation", "id"]
+ "keywords": [
+ "country",
+ "nation",
+ "id"
+ ]
},
"flag_ie": {
"unicode": "1F1EE-1F1EA",
@@ -4932,9 +10063,17 @@
"name": "ireland",
"shortname": ":flag_ie:",
"category": "flags",
- "aliases": [":ie:"],
- "aliases_ascii": [],
- "keywords": ["country", "nation", "&eacute;ire", "eire", "ie"]
+ "aliases": [
+ ":ie:"
+ ],
+ "aliases_ascii": [],
+ "keywords": [
+ "country",
+ "nation",
+ "&eacute;ire",
+ "eire",
+ "ie"
+ ]
},
"flag_il": {
"unicode": "1F1EE-1F1F1",
@@ -4942,9 +10081,29 @@
"name": "israel",
"shortname": ":flag_il:",
"category": "flags",
- "aliases": [":il:"],
+ "aliases": [
+ ":il:"
+ ],
+ "aliases_ascii": [],
+ "keywords": [
+ "country",
+ "nation",
+ "yisra'el",
+ "yisrael",
+ "il"
+ ]
+ },
+ "flag_im": {
+ "unicode": "1F1EE-1F1F2",
+ "unicode_alternates": "",
+ "name": "isle of man",
+ "shortname": ":flag_im:",
+ "category": "flags",
+ "aliases": [
+ ":im:"
+ ],
"aliases_ascii": [],
- "keywords": ["country", "nation", "yisra'el", "yisrael", "il"]
+ "keywords": []
},
"flag_in": {
"unicode": "1F1EE-1F1F3",
@@ -4952,9 +10111,28 @@
"name": "india",
"shortname": ":flag_in:",
"category": "flags",
- "aliases": [":in:"],
+ "aliases": [
+ ":in:"
+ ],
+ "aliases_ascii": [],
+ "keywords": [
+ "country",
+ "nation",
+ "bharat",
+ "in"
+ ]
+ },
+ "flag_io": {
+ "unicode": "1F1EE-1F1F4",
+ "unicode_alternates": "",
+ "name": "british indian ocean territory",
+ "shortname": ":flag_io:",
+ "category": "flags",
+ "aliases": [
+ ":io:"
+ ],
"aliases_ascii": [],
- "keywords": ["country", "nation", "bharat", "in"]
+ "keywords": []
},
"flag_iq": {
"unicode": "1F1EE-1F1F6",
@@ -4962,9 +10140,15 @@
"name": "iraq",
"shortname": ":flag_iq:",
"category": "flags",
- "aliases": [":iq:"],
+ "aliases": [
+ ":iq:"
+ ],
"aliases_ascii": [],
- "keywords": ["country", "nation", "iq"]
+ "keywords": [
+ "country",
+ "nation",
+ "iq"
+ ]
},
"flag_ir": {
"unicode": "1F1EE-1F1F7",
@@ -4972,9 +10156,15 @@
"name": "iran",
"shortname": ":flag_ir:",
"category": "flags",
- "aliases": [":ir:"],
+ "aliases": [
+ ":ir:"
+ ],
"aliases_ascii": [],
- "keywords": ["country", "nation", "ir"]
+ "keywords": [
+ "country",
+ "nation",
+ "ir"
+ ]
},
"flag_is": {
"unicode": "1F1EE-1F1F8",
@@ -4982,9 +10172,16 @@
"name": "iceland",
"shortname": ":flag_is:",
"category": "flags",
- "aliases": [":is:"],
+ "aliases": [
+ ":is:"
+ ],
"aliases_ascii": [],
- "keywords": ["country", "nation", "lyoveldio island", "is"]
+ "keywords": [
+ "country",
+ "nation",
+ "lyoveldio island",
+ "is"
+ ]
},
"flag_it": {
"unicode": "1F1EE-1F1F9",
@@ -4992,9 +10189,16 @@
"name": "italy",
"shortname": ":flag_it:",
"category": "flags",
- "aliases": [":it:"],
+ "aliases": [
+ ":it:"
+ ],
"aliases_ascii": [],
- "keywords": ["italia", "country", "nation", "it"]
+ "keywords": [
+ "italia",
+ "country",
+ "nation",
+ "it"
+ ]
},
"flag_je": {
"unicode": "1F1EF-1F1EA",
@@ -5002,9 +10206,15 @@
"name": "jersey",
"shortname": ":flag_je:",
"category": "flags",
- "aliases": [":je:"],
+ "aliases": [
+ ":je:"
+ ],
"aliases_ascii": [],
- "keywords": ["country", "nation", "je"]
+ "keywords": [
+ "country",
+ "nation",
+ "je"
+ ]
},
"flag_jm": {
"unicode": "1F1EF-1F1F2",
@@ -5012,9 +10222,15 @@
"name": "jamaica",
"shortname": ":flag_jm:",
"category": "flags",
- "aliases": [":jm:"],
+ "aliases": [
+ ":jm:"
+ ],
"aliases_ascii": [],
- "keywords": ["country", "nation", "jm"]
+ "keywords": [
+ "country",
+ "nation",
+ "jm"
+ ]
},
"flag_jo": {
"unicode": "1F1EF-1F1F4",
@@ -5022,9 +10238,16 @@
"name": "jordan",
"shortname": ":flag_jo:",
"category": "flags",
- "aliases": [":jo:"],
+ "aliases": [
+ ":jo:"
+ ],
"aliases_ascii": [],
- "keywords": ["country", "nation", "al urdun", "jo"]
+ "keywords": [
+ "country",
+ "nation",
+ "al urdun",
+ "jo"
+ ]
},
"flag_jp": {
"unicode": "1F1EF-1F1F5",
@@ -5032,9 +10255,16 @@
"name": "japan",
"shortname": ":flag_jp:",
"category": "flags",
- "aliases": [":jp:"],
+ "aliases": [
+ ":jp:"
+ ],
"aliases_ascii": [],
- "keywords": ["nation", "nippon", "country", "jp"]
+ "keywords": [
+ "nation",
+ "nippon",
+ "country",
+ "jp"
+ ]
},
"flag_ke": {
"unicode": "1F1F0-1F1EA",
@@ -5042,9 +10272,15 @@
"name": "kenya",
"shortname": ":flag_ke:",
"category": "flags",
- "aliases": [":ke:"],
+ "aliases": [
+ ":ke:"
+ ],
"aliases_ascii": [],
- "keywords": ["country", "nation", "ke"]
+ "keywords": [
+ "country",
+ "nation",
+ "ke"
+ ]
},
"flag_kg": {
"unicode": "1F1F0-1F1EC",
@@ -5052,9 +10288,16 @@
"name": "kyrgyzstan",
"shortname": ":flag_kg:",
"category": "flags",
- "aliases": [":kg:"],
+ "aliases": [
+ ":kg:"
+ ],
"aliases_ascii": [],
- "keywords": ["country", "nation", "kyrgyz respublikasy", "kg"]
+ "keywords": [
+ "country",
+ "nation",
+ "kyrgyz respublikasy",
+ "kg"
+ ]
},
"flag_kh": {
"unicode": "1F1F0-1F1ED",
@@ -5062,9 +10305,16 @@
"name": "cambodia",
"shortname": ":flag_kh:",
"category": "flags",
- "aliases": [":kh:"],
+ "aliases": [
+ ":kh:"
+ ],
"aliases_ascii": [],
- "keywords": ["country", "nation", "kampuchea", "kh"]
+ "keywords": [
+ "country",
+ "nation",
+ "kampuchea",
+ "kh"
+ ]
},
"flag_ki": {
"unicode": "1F1F0-1F1EE",
@@ -5072,9 +10322,17 @@
"name": "kiribati",
"shortname": ":flag_ki:",
"category": "flags",
- "aliases": [":ki:"],
- "aliases_ascii": [],
- "keywords": ["country", "nation", "kiribati", "kiribas", "ki"]
+ "aliases": [
+ ":ki:"
+ ],
+ "aliases_ascii": [],
+ "keywords": [
+ "country",
+ "nation",
+ "kiribati",
+ "kiribas",
+ "ki"
+ ]
},
"flag_km": {
"unicode": "1F1F0-1F1F2",
@@ -5082,9 +10340,15 @@
"name": "the comoros",
"shortname": ":flag_km:",
"category": "flags",
- "aliases": [":km:"],
+ "aliases": [
+ ":km:"
+ ],
"aliases_ascii": [],
- "keywords": ["country", "nation", "km"]
+ "keywords": [
+ "country",
+ "nation",
+ "km"
+ ]
},
"flag_kn": {
"unicode": "1F1F0-1F1F3",
@@ -5092,9 +10356,15 @@
"name": "saint kitts and nevis",
"shortname": ":flag_kn:",
"category": "flags",
- "aliases": [":kn:"],
+ "aliases": [
+ ":kn:"
+ ],
"aliases_ascii": [],
- "keywords": ["country", "nation", "kn"]
+ "keywords": [
+ "country",
+ "nation",
+ "kn"
+ ]
},
"flag_kp": {
"unicode": "1F1F0-1F1F5",
@@ -5102,9 +10372,15 @@
"name": "north korea",
"shortname": ":flag_kp:",
"category": "flags",
- "aliases": [":kp:"],
+ "aliases": [
+ ":kp:"
+ ],
"aliases_ascii": [],
- "keywords": ["country", "nation", "kp"]
+ "keywords": [
+ "country",
+ "nation",
+ "kp"
+ ]
},
"flag_kr": {
"unicode": "1F1F0-1F1F7",
@@ -5112,9 +10388,16 @@
"name": "korea",
"shortname": ":flag_kr:",
"category": "flags",
- "aliases": [":kr:"],
+ "aliases": [
+ ":kr:"
+ ],
"aliases_ascii": [],
- "keywords": ["nation", "country", "south korea", "kr"]
+ "keywords": [
+ "nation",
+ "country",
+ "south korea",
+ "kr"
+ ]
},
"flag_kw": {
"unicode": "1F1F0-1F1FC",
@@ -5122,9 +10405,16 @@
"name": "kuwait",
"shortname": ":flag_kw:",
"category": "flags",
- "aliases": [":kw:"],
+ "aliases": [
+ ":kw:"
+ ],
"aliases_ascii": [],
- "keywords": ["country", "nation", "al kuwayt", "kw"]
+ "keywords": [
+ "country",
+ "nation",
+ "al kuwayt",
+ "kw"
+ ]
},
"flag_ky": {
"unicode": "1F1F0-1F1FE",
@@ -5132,9 +10422,15 @@
"name": "cayman islands",
"shortname": ":flag_ky:",
"category": "flags",
- "aliases": [":ky:"],
+ "aliases": [
+ ":ky:"
+ ],
"aliases_ascii": [],
- "keywords": ["country", "nation", "ky"]
+ "keywords": [
+ "country",
+ "nation",
+ "ky"
+ ]
},
"flag_kz": {
"unicode": "1F1F0-1F1FF",
@@ -5142,9 +10438,16 @@
"name": "kazakhstan",
"shortname": ":flag_kz:",
"category": "flags",
- "aliases": [":kz:"],
+ "aliases": [
+ ":kz:"
+ ],
"aliases_ascii": [],
- "keywords": ["country", "nation", "qazaqstan", "kz"]
+ "keywords": [
+ "country",
+ "nation",
+ "qazaqstan",
+ "kz"
+ ]
},
"flag_la": {
"unicode": "1F1F1-1F1E6",
@@ -5152,9 +10455,15 @@
"name": "laos",
"shortname": ":flag_la:",
"category": "flags",
- "aliases": [":la:"],
+ "aliases": [
+ ":la:"
+ ],
"aliases_ascii": [],
- "keywords": ["country", "nation", "la"]
+ "keywords": [
+ "country",
+ "nation",
+ "la"
+ ]
},
"flag_lb": {
"unicode": "1F1F1-1F1E7",
@@ -5162,9 +10471,16 @@
"name": "lebanon",
"shortname": ":flag_lb:",
"category": "flags",
- "aliases": [":lb:"],
+ "aliases": [
+ ":lb:"
+ ],
"aliases_ascii": [],
- "keywords": ["country", "nation", "lubnan", "lb"]
+ "keywords": [
+ "country",
+ "nation",
+ "lubnan",
+ "lb"
+ ]
},
"flag_lc": {
"unicode": "1F1F1-1F1E8",
@@ -5172,9 +10488,15 @@
"name": "saint lucia",
"shortname": ":flag_lc:",
"category": "flags",
- "aliases": [":lc:"],
+ "aliases": [
+ ":lc:"
+ ],
"aliases_ascii": [],
- "keywords": ["country", "nation", "lc"]
+ "keywords": [
+ "country",
+ "nation",
+ "lc"
+ ]
},
"flag_li": {
"unicode": "1F1F1-1F1EE",
@@ -5182,9 +10504,15 @@
"name": "liechtenstein",
"shortname": ":flag_li:",
"category": "flags",
- "aliases": [":li:"],
+ "aliases": [
+ ":li:"
+ ],
"aliases_ascii": [],
- "keywords": ["country", "nation", "li"]
+ "keywords": [
+ "country",
+ "nation",
+ "li"
+ ]
},
"flag_lk": {
"unicode": "1F1F1-1F1F0",
@@ -5192,9 +10520,15 @@
"name": "sri lanka",
"shortname": ":flag_lk:",
"category": "flags",
- "aliases": [":lk:"],
+ "aliases": [
+ ":lk:"
+ ],
"aliases_ascii": [],
- "keywords": ["country", "nation", "lk"]
+ "keywords": [
+ "country",
+ "nation",
+ "lk"
+ ]
},
"flag_lr": {
"unicode": "1F1F1-1F1F7",
@@ -5202,9 +10536,15 @@
"name": "liberia",
"shortname": ":flag_lr:",
"category": "flags",
- "aliases": [":lr:"],
+ "aliases": [
+ ":lr:"
+ ],
"aliases_ascii": [],
- "keywords": ["country", "nation", "lr"]
+ "keywords": [
+ "country",
+ "nation",
+ "lr"
+ ]
},
"flag_ls": {
"unicode": "1F1F1-1F1F8",
@@ -5212,9 +10552,15 @@
"name": "lesotho",
"shortname": ":flag_ls:",
"category": "flags",
- "aliases": [":ls:"],
+ "aliases": [
+ ":ls:"
+ ],
"aliases_ascii": [],
- "keywords": ["country", "nation", "ls"]
+ "keywords": [
+ "country",
+ "nation",
+ "ls"
+ ]
},
"flag_lt": {
"unicode": "1F1F1-1F1F9",
@@ -5222,9 +10568,16 @@
"name": "lithuania",
"shortname": ":flag_lt:",
"category": "flags",
- "aliases": [":lt:"],
+ "aliases": [
+ ":lt:"
+ ],
"aliases_ascii": [],
- "keywords": ["country", "nation", "lietuva", "lt"]
+ "keywords": [
+ "country",
+ "nation",
+ "lietuva",
+ "lt"
+ ]
},
"flag_lu": {
"unicode": "1F1F1-1F1FA",
@@ -5232,9 +10585,17 @@
"name": "luxembourg",
"shortname": ":flag_lu:",
"category": "flags",
- "aliases": [":lu:"],
- "aliases_ascii": [],
- "keywords": ["country", "nation", "luxembourg", "letzebuerg", "lu"]
+ "aliases": [
+ ":lu:"
+ ],
+ "aliases_ascii": [],
+ "keywords": [
+ "country",
+ "nation",
+ "luxembourg",
+ "letzebuerg",
+ "lu"
+ ]
},
"flag_lv": {
"unicode": "1F1F1-1F1FB",
@@ -5242,9 +10603,16 @@
"name": "latvia",
"shortname": ":flag_lv:",
"category": "flags",
- "aliases": [":lv:"],
+ "aliases": [
+ ":lv:"
+ ],
"aliases_ascii": [],
- "keywords": ["country", "nation", "latvija", "lv"]
+ "keywords": [
+ "country",
+ "nation",
+ "latvija",
+ "lv"
+ ]
},
"flag_ly": {
"unicode": "1F1F1-1F1FE",
@@ -5252,9 +10620,16 @@
"name": "libya",
"shortname": ":flag_ly:",
"category": "flags",
- "aliases": [":ly:"],
+ "aliases": [
+ ":ly:"
+ ],
"aliases_ascii": [],
- "keywords": ["country", "nation", "libiyah", "ly"]
+ "keywords": [
+ "country",
+ "nation",
+ "libiyah",
+ "ly"
+ ]
},
"flag_ma": {
"unicode": "1F1F2-1F1E6",
@@ -5262,9 +10637,16 @@
"name": "morocco",
"shortname": ":flag_ma:",
"category": "flags",
- "aliases": [":ma:"],
+ "aliases": [
+ ":ma:"
+ ],
"aliases_ascii": [],
- "keywords": ["country", "nation", "al maghrib", "ma"]
+ "keywords": [
+ "country",
+ "nation",
+ "al maghrib",
+ "ma"
+ ]
},
"flag_mc": {
"unicode": "1F1F2-1F1E8",
@@ -5272,9 +10654,15 @@
"name": "monaco",
"shortname": ":flag_mc:",
"category": "flags",
- "aliases": [":mc:"],
+ "aliases": [
+ ":mc:"
+ ],
"aliases_ascii": [],
- "keywords": ["country", "nation", "mc"]
+ "keywords": [
+ "country",
+ "nation",
+ "mc"
+ ]
},
"flag_md": {
"unicode": "1F1F2-1F1E9",
@@ -5282,9 +10670,15 @@
"name": "moldova",
"shortname": ":flag_md:",
"category": "flags",
- "aliases": [":md:"],
+ "aliases": [
+ ":md:"
+ ],
"aliases_ascii": [],
- "keywords": ["country", "nation", "md"]
+ "keywords": [
+ "country",
+ "nation",
+ "md"
+ ]
},
"flag_me": {
"unicode": "1F1F2-1F1EA",
@@ -5292,9 +10686,28 @@
"name": "montenegro",
"shortname": ":flag_me:",
"category": "flags",
- "aliases": [":me:"],
+ "aliases": [
+ ":me:"
+ ],
+ "aliases_ascii": [],
+ "keywords": [
+ "country",
+ "nation",
+ "crna gora",
+ "me"
+ ]
+ },
+ "flag_mf": {
+ "unicode": "1F1F2-1F1EB",
+ "unicode_alternates": "",
+ "name": "saint martin",
+ "shortname": ":flag_mf:",
+ "category": "flags",
+ "aliases": [
+ ":mf:"
+ ],
"aliases_ascii": [],
- "keywords": ["country", "nation", "crna gora", "me"]
+ "keywords": []
},
"flag_mg": {
"unicode": "1F1F2-1F1EC",
@@ -5302,9 +10715,15 @@
"name": "madagascar",
"shortname": ":flag_mg:",
"category": "flags",
- "aliases": [":mg:"],
+ "aliases": [
+ ":mg:"
+ ],
"aliases_ascii": [],
- "keywords": ["country", "nation", "mg"]
+ "keywords": [
+ "country",
+ "nation",
+ "mg"
+ ]
},
"flag_mh": {
"unicode": "1F1F2-1F1ED",
@@ -5312,9 +10731,15 @@
"name": "the marshall islands",
"shortname": ":flag_mh:",
"category": "flags",
- "aliases": [":mh:"],
+ "aliases": [
+ ":mh:"
+ ],
"aliases_ascii": [],
- "keywords": ["country", "nation", "mh"]
+ "keywords": [
+ "country",
+ "nation",
+ "mh"
+ ]
},
"flag_mk": {
"unicode": "1F1F2-1F1F0",
@@ -5322,9 +10747,15 @@
"name": "macedonia",
"shortname": ":flag_mk:",
"category": "flags",
- "aliases": [":mk:"],
+ "aliases": [
+ ":mk:"
+ ],
"aliases_ascii": [],
- "keywords": ["country", "nation", "mk"]
+ "keywords": [
+ "country",
+ "nation",
+ "mk"
+ ]
},
"flag_ml": {
"unicode": "1F1F2-1F1F1",
@@ -5332,9 +10763,15 @@
"name": "mali",
"shortname": ":flag_ml:",
"category": "flags",
- "aliases": [":ml:"],
+ "aliases": [
+ ":ml:"
+ ],
"aliases_ascii": [],
- "keywords": ["country", "nation", "ml"]
+ "keywords": [
+ "country",
+ "nation",
+ "ml"
+ ]
},
"flag_mm": {
"unicode": "1F1F2-1F1F2",
@@ -5342,9 +10779,16 @@
"name": "myanmar",
"shortname": ":flag_mm:",
"category": "flags",
- "aliases": [":mm:"],
+ "aliases": [
+ ":mm:"
+ ],
"aliases_ascii": [],
- "keywords": ["country", "nation", "myanma naingngandaw", "mm"]
+ "keywords": [
+ "country",
+ "nation",
+ "myanma naingngandaw",
+ "mm"
+ ]
},
"flag_mn": {
"unicode": "1F1F2-1F1F3",
@@ -5352,9 +10796,16 @@
"name": "mongolia",
"shortname": ":flag_mn:",
"category": "flags",
- "aliases": [":mn:"],
+ "aliases": [
+ ":mn:"
+ ],
"aliases_ascii": [],
- "keywords": ["country", "nation", "mongol uls", "mn"]
+ "keywords": [
+ "country",
+ "nation",
+ "mongol uls",
+ "mn"
+ ]
},
"flag_mo": {
"unicode": "1F1F2-1F1F4",
@@ -5362,9 +10813,40 @@
"name": "macau",
"shortname": ":flag_mo:",
"category": "flags",
- "aliases": [":mo:"],
+ "aliases": [
+ ":mo:"
+ ],
+ "aliases_ascii": [],
+ "keywords": [
+ "country",
+ "nation",
+ "aomen",
+ "mo"
+ ]
+ },
+ "flag_mp": {
+ "unicode": "1F1F2-1F1F5",
+ "unicode_alternates": "",
+ "name": "northern mariana islands",
+ "shortname": ":flag_mp:",
+ "category": "flags",
+ "aliases": [
+ ":mp:"
+ ],
"aliases_ascii": [],
- "keywords": ["country", "nation", "aomen", "mo"]
+ "keywords": []
+ },
+ "flag_mq": {
+ "unicode": "1F1F2-1F1F6",
+ "unicode_alternates": "",
+ "name": "martinique",
+ "shortname": ":flag_mq:",
+ "category": "flags",
+ "aliases": [
+ ":mq:"
+ ],
+ "aliases_ascii": [],
+ "keywords": []
},
"flag_mr": {
"unicode": "1F1F2-1F1F7",
@@ -5372,9 +10854,16 @@
"name": "mauritania",
"shortname": ":flag_mr:",
"category": "flags",
- "aliases": [":mr:"],
+ "aliases": [
+ ":mr:"
+ ],
"aliases_ascii": [],
- "keywords": ["country", "nation", "muritaniyah", "mr"]
+ "keywords": [
+ "country",
+ "nation",
+ "muritaniyah",
+ "mr"
+ ]
},
"flag_ms": {
"unicode": "1F1F2-1F1F8",
@@ -5382,9 +10871,15 @@
"name": "montserrat",
"shortname": ":flag_ms:",
"category": "flags",
- "aliases": [":ms:"],
+ "aliases": [
+ ":ms:"
+ ],
"aliases_ascii": [],
- "keywords": ["country", "nation", "ms"]
+ "keywords": [
+ "country",
+ "nation",
+ "ms"
+ ]
},
"flag_mt": {
"unicode": "1F1F2-1F1F9",
@@ -5392,9 +10887,15 @@
"name": "malta",
"shortname": ":flag_mt:",
"category": "flags",
- "aliases": [":mt:"],
+ "aliases": [
+ ":mt:"
+ ],
"aliases_ascii": [],
- "keywords": ["country", "nation", "mt"]
+ "keywords": [
+ "country",
+ "nation",
+ "mt"
+ ]
},
"flag_mu": {
"unicode": "1F1F2-1F1FA",
@@ -5402,9 +10903,15 @@
"name": "mauritius",
"shortname": ":flag_mu:",
"category": "flags",
- "aliases": [":mu:"],
+ "aliases": [
+ ":mu:"
+ ],
"aliases_ascii": [],
- "keywords": ["country", "nation", "mu"]
+ "keywords": [
+ "country",
+ "nation",
+ "mu"
+ ]
},
"flag_mv": {
"unicode": "1F1F2-1F1FB",
@@ -5412,9 +10919,16 @@
"name": "maldives",
"shortname": ":flag_mv:",
"category": "flags",
- "aliases": [":mv:"],
+ "aliases": [
+ ":mv:"
+ ],
"aliases_ascii": [],
- "keywords": ["country", "nation", "dhivehi raajje", "mv"]
+ "keywords": [
+ "country",
+ "nation",
+ "dhivehi raajje",
+ "mv"
+ ]
},
"flag_mw": {
"unicode": "1F1F2-1F1FC",
@@ -5422,9 +10936,15 @@
"name": "malawi",
"shortname": ":flag_mw:",
"category": "flags",
- "aliases": [":mw:"],
+ "aliases": [
+ ":mw:"
+ ],
"aliases_ascii": [],
- "keywords": ["country", "nation", "mw"]
+ "keywords": [
+ "country",
+ "nation",
+ "mw"
+ ]
},
"flag_mx": {
"unicode": "1F1F2-1F1FD",
@@ -5432,9 +10952,15 @@
"name": "mexico",
"shortname": ":flag_mx:",
"category": "flags",
- "aliases": [":mx:"],
+ "aliases": [
+ ":mx:"
+ ],
"aliases_ascii": [],
- "keywords": ["country", "nation", "mx"]
+ "keywords": [
+ "country",
+ "nation",
+ "mx"
+ ]
},
"flag_my": {
"unicode": "1F1F2-1F1FE",
@@ -5442,9 +10968,15 @@
"name": "malaysia",
"shortname": ":flag_my:",
"category": "flags",
- "aliases": [":my:"],
+ "aliases": [
+ ":my:"
+ ],
"aliases_ascii": [],
- "keywords": ["country", "nation", "my"]
+ "keywords": [
+ "country",
+ "nation",
+ "my"
+ ]
},
"flag_mz": {
"unicode": "1F1F2-1F1FF",
@@ -5452,9 +10984,16 @@
"name": "mozambique",
"shortname": ":flag_mz:",
"category": "flags",
- "aliases": [":mz:"],
+ "aliases": [
+ ":mz:"
+ ],
"aliases_ascii": [],
- "keywords": ["country", "nation", "mocambique", "mz"]
+ "keywords": [
+ "country",
+ "nation",
+ "mocambique",
+ "mz"
+ ]
},
"flag_na": {
"unicode": "1F1F3-1F1E6",
@@ -5462,9 +11001,15 @@
"name": "namibia",
"shortname": ":flag_na:",
"category": "flags",
- "aliases": [":na:"],
+ "aliases": [
+ ":na:"
+ ],
"aliases_ascii": [],
- "keywords": ["country", "nation", "na"]
+ "keywords": [
+ "country",
+ "nation",
+ "na"
+ ]
},
"flag_nc": {
"unicode": "1F1F3-1F1E8",
@@ -5472,9 +11017,18 @@
"name": "new caledonia",
"shortname": ":flag_nc:",
"category": "flags",
- "aliases": [":nc:"],
- "aliases_ascii": [],
- "keywords": ["country", "nation", "nouvelle", "cal&eacute;donie", "caledonie", "nc"]
+ "aliases": [
+ ":nc:"
+ ],
+ "aliases_ascii": [],
+ "keywords": [
+ "country",
+ "nation",
+ "nouvelle",
+ "cal&eacute;donie",
+ "caledonie",
+ "nc"
+ ]
},
"flag_ne": {
"unicode": "1F1F3-1F1EA",
@@ -5482,9 +11036,27 @@
"name": "niger",
"shortname": ":flag_ne:",
"category": "flags",
- "aliases": [":ne:"],
+ "aliases": [
+ ":ne:"
+ ],
+ "aliases_ascii": [],
+ "keywords": [
+ "country",
+ "nation",
+ "ne"
+ ]
+ },
+ "flag_nf": {
+ "unicode": "1F1F3-1F1EB",
+ "unicode_alternates": "",
+ "name": "norfolk island",
+ "shortname": ":flag_nf:",
+ "category": "flags",
+ "aliases": [
+ ":nf:"
+ ],
"aliases_ascii": [],
- "keywords": ["country", "nation", "ne"]
+ "keywords": []
},
"flag_ng": {
"unicode": "1F1F3-1F1EC",
@@ -5492,9 +11064,15 @@
"name": "nigeria",
"shortname": ":flag_ng:",
"category": "flags",
- "aliases": [":nigeria:"],
+ "aliases": [
+ ":nigeria:"
+ ],
"aliases_ascii": [],
- "keywords": ["country", "nation", "ng"]
+ "keywords": [
+ "country",
+ "nation",
+ "ng"
+ ]
},
"flag_ni": {
"unicode": "1F1F3-1F1EE",
@@ -5502,9 +11080,15 @@
"name": "nicaragua",
"shortname": ":flag_ni:",
"category": "flags",
- "aliases": [":ni:"],
+ "aliases": [
+ ":ni:"
+ ],
"aliases_ascii": [],
- "keywords": ["country", "nation", "ni"]
+ "keywords": [
+ "country",
+ "nation",
+ "ni"
+ ]
},
"flag_nl": {
"unicode": "1F1F3-1F1F1",
@@ -5512,9 +11096,17 @@
"name": "the netherlands",
"shortname": ":flag_nl:",
"category": "flags",
- "aliases": [":nl:"],
- "aliases_ascii": [],
- "keywords": ["country", "nation", "nederland", "holland", "nl"]
+ "aliases": [
+ ":nl:"
+ ],
+ "aliases_ascii": [],
+ "keywords": [
+ "country",
+ "nation",
+ "nederland",
+ "holland",
+ "nl"
+ ]
},
"flag_no": {
"unicode": "1F1F3-1F1F4",
@@ -5522,9 +11114,16 @@
"name": "norway",
"shortname": ":flag_no:",
"category": "flags",
- "aliases": [":no:"],
+ "aliases": [
+ ":no:"
+ ],
"aliases_ascii": [],
- "keywords": ["country", "nation", "norge", "no"]
+ "keywords": [
+ "country",
+ "nation",
+ "norge",
+ "no"
+ ]
},
"flag_np": {
"unicode": "1F1F3-1F1F5",
@@ -5532,9 +11131,15 @@
"name": "nepal",
"shortname": ":flag_np:",
"category": "flags",
- "aliases": [":np:"],
+ "aliases": [
+ ":np:"
+ ],
"aliases_ascii": [],
- "keywords": ["country", "nation", "np"]
+ "keywords": [
+ "country",
+ "nation",
+ "np"
+ ]
},
"flag_nr": {
"unicode": "1F1F3-1F1F7",
@@ -5542,9 +11147,15 @@
"name": "nauru",
"shortname": ":flag_nr:",
"category": "flags",
- "aliases": [":nr:"],
+ "aliases": [
+ ":nr:"
+ ],
"aliases_ascii": [],
- "keywords": ["country", "nation", "nr"]
+ "keywords": [
+ "country",
+ "nation",
+ "nr"
+ ]
},
"flag_nu": {
"unicode": "1F1F3-1F1FA",
@@ -5552,9 +11163,15 @@
"name": "niue",
"shortname": ":flag_nu:",
"category": "flags",
- "aliases": [":nu:"],
+ "aliases": [
+ ":nu:"
+ ],
"aliases_ascii": [],
- "keywords": ["country", "nation", "nu"]
+ "keywords": [
+ "country",
+ "nation",
+ "nu"
+ ]
},
"flag_nz": {
"unicode": "1F1F3-1F1FF",
@@ -5562,9 +11179,16 @@
"name": "new zealand",
"shortname": ":flag_nz:",
"category": "flags",
- "aliases": [":nz:"],
+ "aliases": [
+ ":nz:"
+ ],
"aliases_ascii": [],
- "keywords": ["country", "nation", "aotearoa", "nz"]
+ "keywords": [
+ "country",
+ "nation",
+ "aotearoa",
+ "nz"
+ ]
},
"flag_om": {
"unicode": "1F1F4-1F1F2",
@@ -5572,9 +11196,16 @@
"name": "oman",
"shortname": ":flag_om:",
"category": "flags",
- "aliases": [":om:"],
+ "aliases": [
+ ":om:"
+ ],
"aliases_ascii": [],
- "keywords": ["country", "nation", "saltanat uman", "om"]
+ "keywords": [
+ "country",
+ "nation",
+ "saltanat uman",
+ "om"
+ ]
},
"flag_pa": {
"unicode": "1F1F5-1F1E6",
@@ -5582,9 +11213,15 @@
"name": "panama",
"shortname": ":flag_pa:",
"category": "flags",
- "aliases": [":pa:"],
+ "aliases": [
+ ":pa:"
+ ],
"aliases_ascii": [],
- "keywords": ["country", "nation", "pa"]
+ "keywords": [
+ "country",
+ "nation",
+ "pa"
+ ]
},
"flag_pe": {
"unicode": "1F1F5-1F1EA",
@@ -5592,9 +11229,15 @@
"name": "peru",
"shortname": ":flag_pe:",
"category": "flags",
- "aliases": [":pe:"],
+ "aliases": [
+ ":pe:"
+ ],
"aliases_ascii": [],
- "keywords": ["country", "nation", "pe"]
+ "keywords": [
+ "country",
+ "nation",
+ "pe"
+ ]
},
"flag_pf": {
"unicode": "1F1F5-1F1EB",
@@ -5602,9 +11245,17 @@
"name": "french polynesia",
"shortname": ":flag_pf:",
"category": "flags",
- "aliases": [":pf:"],
- "aliases_ascii": [],
- "keywords": ["country", "nation", "polyn&eacute;sie fran&ccedil;aise", "polynesie francaise", "pf"]
+ "aliases": [
+ ":pf:"
+ ],
+ "aliases_ascii": [],
+ "keywords": [
+ "country",
+ "nation",
+ "polyn&eacute;sie fran&ccedil;aise",
+ "polynesie francaise",
+ "pf"
+ ]
},
"flag_pg": {
"unicode": "1F1F5-1F1EC",
@@ -5612,9 +11263,16 @@
"name": "papua new guinea",
"shortname": ":flag_pg:",
"category": "flags",
- "aliases": [":pg:"],
+ "aliases": [
+ ":pg:"
+ ],
"aliases_ascii": [],
- "keywords": ["country", "nation", "papua niu gini", "pg"]
+ "keywords": [
+ "country",
+ "nation",
+ "papua niu gini",
+ "pg"
+ ]
},
"flag_ph": {
"unicode": "1F1F5-1F1ED",
@@ -5622,9 +11280,16 @@
"name": "the philippines",
"shortname": ":flag_ph:",
"category": "flags",
- "aliases": [":ph:"],
+ "aliases": [
+ ":ph:"
+ ],
"aliases_ascii": [],
- "keywords": ["country", "nation", "pilipinas", "ph"]
+ "keywords": [
+ "country",
+ "nation",
+ "pilipinas",
+ "ph"
+ ]
},
"flag_pk": {
"unicode": "1F1F5-1F1F0",
@@ -5632,9 +11297,15 @@
"name": "pakistan",
"shortname": ":flag_pk:",
"category": "flags",
- "aliases": [":pk:"],
+ "aliases": [
+ ":pk:"
+ ],
"aliases_ascii": [],
- "keywords": ["country", "nation", "pk"]
+ "keywords": [
+ "country",
+ "nation",
+ "pk"
+ ]
},
"flag_pl": {
"unicode": "1F1F5-1F1F1",
@@ -5642,9 +11313,40 @@
"name": "poland",
"shortname": ":flag_pl:",
"category": "flags",
- "aliases": [":pl:"],
+ "aliases": [
+ ":pl:"
+ ],
+ "aliases_ascii": [],
+ "keywords": [
+ "country",
+ "nation",
+ "polska",
+ "pl"
+ ]
+ },
+ "flag_pm": {
+ "unicode": "1F1F5-1F1F2",
+ "unicode_alternates": "",
+ "name": "saint pierre and miquelon",
+ "shortname": ":flag_pm:",
+ "category": "flags",
+ "aliases": [
+ ":pm:"
+ ],
+ "aliases_ascii": [],
+ "keywords": []
+ },
+ "flag_pn": {
+ "unicode": "1F1F5-1F1F3",
+ "unicode_alternates": "",
+ "name": "pitcairn",
+ "shortname": ":flag_pn:",
+ "category": "flags",
+ "aliases": [
+ ":pn:"
+ ],
"aliases_ascii": [],
- "keywords": ["country", "nation", "polska", "pl"]
+ "keywords": []
},
"flag_pr": {
"unicode": "1F1F5-1F1F7",
@@ -5652,9 +11354,15 @@
"name": "puerto rico",
"shortname": ":flag_pr:",
"category": "flags",
- "aliases": [":pr:"],
+ "aliases": [
+ ":pr:"
+ ],
"aliases_ascii": [],
- "keywords": ["country", "nation", "pr"]
+ "keywords": [
+ "country",
+ "nation",
+ "pr"
+ ]
},
"flag_ps": {
"unicode": "1F1F5-1F1F8",
@@ -5662,9 +11370,15 @@
"name": "palestinian authority",
"shortname": ":flag_ps:",
"category": "flags",
- "aliases": [":ps:"],
+ "aliases": [
+ ":ps:"
+ ],
"aliases_ascii": [],
- "keywords": ["country", "nation", "ps"]
+ "keywords": [
+ "country",
+ "nation",
+ "ps"
+ ]
},
"flag_pt": {
"unicode": "1F1F5-1F1F9",
@@ -5672,9 +11386,15 @@
"name": "portugal",
"shortname": ":flag_pt:",
"category": "flags",
- "aliases": [":pt:"],
+ "aliases": [
+ ":pt:"
+ ],
"aliases_ascii": [],
- "keywords": ["country", "nation", "pt"]
+ "keywords": [
+ "country",
+ "nation",
+ "pt"
+ ]
},
"flag_pw": {
"unicode": "1F1F5-1F1FC",
@@ -5682,9 +11402,16 @@
"name": "palau",
"shortname": ":flag_pw:",
"category": "flags",
- "aliases": [":pw:"],
+ "aliases": [
+ ":pw:"
+ ],
"aliases_ascii": [],
- "keywords": ["country", "nation", "belau", "pw"]
+ "keywords": [
+ "country",
+ "nation",
+ "belau",
+ "pw"
+ ]
},
"flag_py": {
"unicode": "1F1F5-1F1FE",
@@ -5692,9 +11419,15 @@
"name": "paraguay",
"shortname": ":flag_py:",
"category": "flags",
- "aliases": [":py:"],
+ "aliases": [
+ ":py:"
+ ],
"aliases_ascii": [],
- "keywords": ["country", "nation", "py"]
+ "keywords": [
+ "country",
+ "nation",
+ "py"
+ ]
},
"flag_qa": {
"unicode": "1F1F6-1F1E6",
@@ -5702,9 +11435,28 @@
"name": "qatar",
"shortname": ":flag_qa:",
"category": "flags",
- "aliases": [":qa:"],
+ "aliases": [
+ ":qa:"
+ ],
+ "aliases_ascii": [],
+ "keywords": [
+ "country",
+ "nation",
+ "dawlat qatar",
+ "qa"
+ ]
+ },
+ "flag_re": {
+ "unicode": "1F1F7-1F1EA",
+ "unicode_alternates": "",
+ "name": "réunion",
+ "shortname": ":flag_re:",
+ "category": "flags",
+ "aliases": [
+ ":re:"
+ ],
"aliases_ascii": [],
- "keywords": ["country", "nation", "dawlat qatar", "qa"]
+ "keywords": []
},
"flag_ro": {
"unicode": "1F1F7-1F1F4",
@@ -5712,9 +11464,15 @@
"name": "romania",
"shortname": ":flag_ro:",
"category": "flags",
- "aliases": [":ro:"],
+ "aliases": [
+ ":ro:"
+ ],
"aliases_ascii": [],
- "keywords": ["country", "nation", "ro"]
+ "keywords": [
+ "country",
+ "nation",
+ "ro"
+ ]
},
"flag_rs": {
"unicode": "1F1F7-1F1F8",
@@ -5722,9 +11480,16 @@
"name": "serbia",
"shortname": ":flag_rs:",
"category": "flags",
- "aliases": [":rs:"],
+ "aliases": [
+ ":rs:"
+ ],
"aliases_ascii": [],
- "keywords": ["country", "nation", "srbija", "rs"]
+ "keywords": [
+ "country",
+ "nation",
+ "srbija",
+ "rs"
+ ]
},
"flag_ru": {
"unicode": "1F1F7-1F1FA",
@@ -5732,9 +11497,16 @@
"name": "russia",
"shortname": ":flag_ru:",
"category": "flags",
- "aliases": [":ru:"],
+ "aliases": [
+ ":ru:"
+ ],
"aliases_ascii": [],
- "keywords": ["nation", "russian", "country", "ru"]
+ "keywords": [
+ "nation",
+ "russian",
+ "country",
+ "ru"
+ ]
},
"flag_rw": {
"unicode": "1F1F7-1F1FC",
@@ -5742,9 +11514,15 @@
"name": "rwanda",
"shortname": ":flag_rw:",
"category": "flags",
- "aliases": [":rw:"],
+ "aliases": [
+ ":rw:"
+ ],
"aliases_ascii": [],
- "keywords": ["country", "nation", "rw"]
+ "keywords": [
+ "country",
+ "nation",
+ "rw"
+ ]
},
"flag_sa": {
"unicode": "1F1F8-1F1E6",
@@ -5752,9 +11530,17 @@
"name": "saudi arabia",
"shortname": ":flag_sa:",
"category": "flags",
- "aliases": [":saudiarabia:", ":saudi:"],
- "aliases_ascii": [],
- "keywords": ["country", "nation", "al arabiyah as suudiyah", "sa"]
+ "aliases": [
+ ":saudiarabia:",
+ ":saudi:"
+ ],
+ "aliases_ascii": [],
+ "keywords": [
+ "country",
+ "nation",
+ "al arabiyah as suudiyah",
+ "sa"
+ ]
},
"flag_sb": {
"unicode": "1F1F8-1F1E7",
@@ -5762,9 +11548,15 @@
"name": "the solomon islands",
"shortname": ":flag_sb:",
"category": "flags",
- "aliases": [":sb:"],
+ "aliases": [
+ ":sb:"
+ ],
"aliases_ascii": [],
- "keywords": ["country", "nation", "sb"]
+ "keywords": [
+ "country",
+ "nation",
+ "sb"
+ ]
},
"flag_sc": {
"unicode": "1F1F8-1F1E8",
@@ -5772,9 +11564,16 @@
"name": "the seychelles",
"shortname": ":flag_sc:",
"category": "flags",
- "aliases": [":sc:"],
+ "aliases": [
+ ":sc:"
+ ],
"aliases_ascii": [],
- "keywords": ["country", "nation", "seychelles", "sc"]
+ "keywords": [
+ "country",
+ "nation",
+ "seychelles",
+ "sc"
+ ]
},
"flag_sd": {
"unicode": "1F1F8-1F1E9",
@@ -5782,9 +11581,16 @@
"name": "sudan",
"shortname": ":flag_sd:",
"category": "flags",
- "aliases": [":sd:"],
+ "aliases": [
+ ":sd:"
+ ],
"aliases_ascii": [],
- "keywords": ["country", "nation", "as-sudan", "sd"]
+ "keywords": [
+ "country",
+ "nation",
+ "as-sudan",
+ "sd"
+ ]
},
"flag_se": {
"unicode": "1F1F8-1F1EA",
@@ -5792,9 +11598,16 @@
"name": "sweden",
"shortname": ":flag_se:",
"category": "flags",
- "aliases": [":se:"],
+ "aliases": [
+ ":se:"
+ ],
"aliases_ascii": [],
- "keywords": ["country", "nation", "sverige", "se"]
+ "keywords": [
+ "country",
+ "nation",
+ "sverige",
+ "se"
+ ]
},
"flag_sg": {
"unicode": "1F1F8-1F1EC",
@@ -5802,9 +11615,15 @@
"name": "singapore",
"shortname": ":flag_sg:",
"category": "flags",
- "aliases": [":sg:"],
+ "aliases": [
+ ":sg:"
+ ],
"aliases_ascii": [],
- "keywords": ["country", "nation", "sg"]
+ "keywords": [
+ "country",
+ "nation",
+ "sg"
+ ]
},
"flag_sh": {
"unicode": "1F1F8-1F1ED",
@@ -5812,9 +11631,15 @@
"name": "saint helena",
"shortname": ":flag_sh:",
"category": "flags",
- "aliases": [":sh:"],
+ "aliases": [
+ ":sh:"
+ ],
"aliases_ascii": [],
- "keywords": ["country", "nation", "sh"]
+ "keywords": [
+ "country",
+ "nation",
+ "sh"
+ ]
},
"flag_si": {
"unicode": "1F1F8-1F1EE",
@@ -5822,9 +11647,28 @@
"name": "slovenia",
"shortname": ":flag_si:",
"category": "flags",
- "aliases": [":si:"],
+ "aliases": [
+ ":si:"
+ ],
+ "aliases_ascii": [],
+ "keywords": [
+ "country",
+ "nation",
+ "slovenija",
+ "si"
+ ]
+ },
+ "flag_sj": {
+ "unicode": "1F1F8-1F1EF",
+ "unicode_alternates": "",
+ "name": "svalbard and jan mayen",
+ "shortname": ":flag_sj:",
+ "category": "flags",
+ "aliases": [
+ ":sj:"
+ ],
"aliases_ascii": [],
- "keywords": ["country", "nation", "slovenija", "si"]
+ "keywords": []
},
"flag_sk": {
"unicode": "1F1F8-1F1F0",
@@ -5832,9 +11676,15 @@
"name": "slovakia",
"shortname": ":flag_sk:",
"category": "flags",
- "aliases": [":sk:"],
+ "aliases": [
+ ":sk:"
+ ],
"aliases_ascii": [],
- "keywords": ["country", "nation", "sk"]
+ "keywords": [
+ "country",
+ "nation",
+ "sk"
+ ]
},
"flag_sl": {
"unicode": "1F1F8-1F1F1",
@@ -5842,9 +11692,15 @@
"name": "sierra leone",
"shortname": ":flag_sl:",
"category": "flags",
- "aliases": [":sl:"],
+ "aliases": [
+ ":sl:"
+ ],
"aliases_ascii": [],
- "keywords": ["country", "nation", "sl"]
+ "keywords": [
+ "country",
+ "nation",
+ "sl"
+ ]
},
"flag_sm": {
"unicode": "1F1F8-1F1F2",
@@ -5852,9 +11708,15 @@
"name": "san marino",
"shortname": ":flag_sm:",
"category": "flags",
- "aliases": [":sm:"],
+ "aliases": [
+ ":sm:"
+ ],
"aliases_ascii": [],
- "keywords": ["country", "nation", "sm"]
+ "keywords": [
+ "country",
+ "nation",
+ "sm"
+ ]
},
"flag_sn": {
"unicode": "1F1F8-1F1F3",
@@ -5862,9 +11724,15 @@
"name": "senegal",
"shortname": ":flag_sn:",
"category": "flags",
- "aliases": [":sn:"],
+ "aliases": [
+ ":sn:"
+ ],
"aliases_ascii": [],
- "keywords": ["country", "nation", "sn"]
+ "keywords": [
+ "country",
+ "nation",
+ "sn"
+ ]
},
"flag_so": {
"unicode": "1F1F8-1F1F4",
@@ -5872,9 +11740,15 @@
"name": "somalia",
"shortname": ":flag_so:",
"category": "flags",
- "aliases": [":so:"],
+ "aliases": [
+ ":so:"
+ ],
"aliases_ascii": [],
- "keywords": ["country", "nation", "so"]
+ "keywords": [
+ "country",
+ "nation",
+ "so"
+ ]
},
"flag_sr": {
"unicode": "1F1F8-1F1F7",
@@ -5882,9 +11756,27 @@
"name": "suriname",
"shortname": ":flag_sr:",
"category": "flags",
- "aliases": [":sr:"],
+ "aliases": [
+ ":sr:"
+ ],
+ "aliases_ascii": [],
+ "keywords": [
+ "country",
+ "nation",
+ "sr"
+ ]
+ },
+ "flag_ss": {
+ "unicode": "1F1F8-1F1F8",
+ "unicode_alternates": "",
+ "name": "south sudan",
+ "shortname": ":flag_ss:",
+ "category": "flags",
+ "aliases": [
+ ":ss:"
+ ],
"aliases_ascii": [],
- "keywords": ["country", "nation", "sr"]
+ "keywords": []
},
"flag_st": {
"unicode": "1F1F8-1F1F9",
@@ -5892,9 +11784,16 @@
"name": "sao tome and principe",
"shortname": ":flag_st:",
"category": "flags",
- "aliases": [":st:"],
+ "aliases": [
+ ":st:"
+ ],
"aliases_ascii": [],
- "keywords": ["country", "nation", "sao tome e principe", "st"]
+ "keywords": [
+ "country",
+ "nation",
+ "sao tome e principe",
+ "st"
+ ]
},
"flag_sv": {
"unicode": "1F1F8-1F1FB",
@@ -5902,9 +11801,27 @@
"name": "el salvador",
"shortname": ":flag_sv:",
"category": "flags",
- "aliases": [":sv:"],
+ "aliases": [
+ ":sv:"
+ ],
+ "aliases_ascii": [],
+ "keywords": [
+ "country",
+ "nation",
+ "sv"
+ ]
+ },
+ "flag_sx": {
+ "unicode": "1F1F8-1F1FD",
+ "unicode_alternates": "",
+ "name": "sint maarten",
+ "shortname": ":flag_sx:",
+ "category": "flags",
+ "aliases": [
+ ":sx:"
+ ],
"aliases_ascii": [],
- "keywords": ["country", "nation", "sv"]
+ "keywords": []
},
"flag_sy": {
"unicode": "1F1F8-1F1FE",
@@ -5912,9 +11829,15 @@
"name": "syria",
"shortname": ":flag_sy:",
"category": "flags",
- "aliases": [":sy:"],
+ "aliases": [
+ ":sy:"
+ ],
"aliases_ascii": [],
- "keywords": ["country", "nation", "sy"]
+ "keywords": [
+ "country",
+ "nation",
+ "sy"
+ ]
},
"flag_sz": {
"unicode": "1F1F8-1F1FF",
@@ -5922,9 +11845,39 @@
"name": "swaziland",
"shortname": ":flag_sz:",
"category": "flags",
- "aliases": [":sz:"],
+ "aliases": [
+ ":sz:"
+ ],
+ "aliases_ascii": [],
+ "keywords": [
+ "country",
+ "nation",
+ "sz"
+ ]
+ },
+ "flag_ta": {
+ "unicode": "1F1F9-1F1E6",
+ "unicode_alternates": "",
+ "name": "tristan da cunha",
+ "shortname": ":flag_ta:",
+ "category": "flags",
+ "aliases": [
+ ":ta:"
+ ],
"aliases_ascii": [],
- "keywords": ["country", "nation", "sz"]
+ "keywords": []
+ },
+ "flag_tc": {
+ "unicode": "1F1F9-1F1E8",
+ "unicode_alternates": "",
+ "name": "turks and caicos islands",
+ "shortname": ":flag_tc:",
+ "category": "flags",
+ "aliases": [
+ ":tc:"
+ ],
+ "aliases_ascii": [],
+ "keywords": []
},
"flag_td": {
"unicode": "1F1F9-1F1E9",
@@ -5932,9 +11885,28 @@
"name": "chad",
"shortname": ":flag_td:",
"category": "flags",
- "aliases": [":td:"],
+ "aliases": [
+ ":td:"
+ ],
+ "aliases_ascii": [],
+ "keywords": [
+ "country",
+ "nation",
+ "tchad",
+ "td"
+ ]
+ },
+ "flag_tf": {
+ "unicode": "1F1F9-1F1EB",
+ "unicode_alternates": "",
+ "name": "french southern territories",
+ "shortname": ":flag_tf:",
+ "category": "flags",
+ "aliases": [
+ ":tf:"
+ ],
"aliases_ascii": [],
- "keywords": ["country", "nation", "tchad", "td"]
+ "keywords": []
},
"flag_tg": {
"unicode": "1F1F9-1F1EC",
@@ -5942,9 +11914,16 @@
"name": "togo",
"shortname": ":flag_tg:",
"category": "flags",
- "aliases": [":tg:"],
+ "aliases": [
+ ":tg:"
+ ],
"aliases_ascii": [],
- "keywords": ["country", "nation", "republique togolaise", "tg"]
+ "keywords": [
+ "country",
+ "nation",
+ "republique togolaise",
+ "tg"
+ ]
},
"flag_th": {
"unicode": "1F1F9-1F1ED",
@@ -5952,9 +11931,16 @@
"name": "thailand",
"shortname": ":flag_th:",
"category": "flags",
- "aliases": [":th:"],
+ "aliases": [
+ ":th:"
+ ],
"aliases_ascii": [],
- "keywords": ["country", "nation", "prathet thai", "th"]
+ "keywords": [
+ "country",
+ "nation",
+ "prathet thai",
+ "th"
+ ]
},
"flag_tj": {
"unicode": "1F1F9-1F1EF",
@@ -5962,9 +11948,28 @@
"name": "tajikistan",
"shortname": ":flag_tj:",
"category": "flags",
- "aliases": [":tj:"],
+ "aliases": [
+ ":tj:"
+ ],
+ "aliases_ascii": [],
+ "keywords": [
+ "country",
+ "nation",
+ "jumhurii tojikiston",
+ "tj"
+ ]
+ },
+ "flag_tk": {
+ "unicode": "1F1F9-1F1F0",
+ "unicode_alternates": "",
+ "name": "tokelau",
+ "shortname": ":flag_tk:",
+ "category": "flags",
+ "aliases": [
+ ":tk:"
+ ],
"aliases_ascii": [],
- "keywords": ["country", "nation", "jumhurii tojikiston", "tj"]
+ "keywords": []
},
"flag_tl": {
"unicode": "1F1F9-1F1F1",
@@ -5972,9 +11977,15 @@
"name": "east timor",
"shortname": ":flag_tl:",
"category": "flags",
- "aliases": [":tl:"],
+ "aliases": [
+ ":tl:"
+ ],
"aliases_ascii": [],
- "keywords": ["country", "nation", "tl"]
+ "keywords": [
+ "country",
+ "nation",
+ "tl"
+ ]
},
"flag_tm": {
"unicode": "1F1F9-1F1F2",
@@ -5982,9 +11993,15 @@
"name": "turkmenistan",
"shortname": ":flag_tm:",
"category": "flags",
- "aliases": [":turkmenistan:"],
+ "aliases": [
+ ":turkmenistan:"
+ ],
"aliases_ascii": [],
- "keywords": ["country", "nation", "tm"]
+ "keywords": [
+ "country",
+ "nation",
+ "tm"
+ ]
},
"flag_tn": {
"unicode": "1F1F9-1F1F3",
@@ -5992,9 +12009,16 @@
"name": "tunisia",
"shortname": ":flag_tn:",
"category": "flags",
- "aliases": [":tn:"],
+ "aliases": [
+ ":tn:"
+ ],
"aliases_ascii": [],
- "keywords": ["country", "nation", "tunis", "tn"]
+ "keywords": [
+ "country",
+ "nation",
+ "tunis",
+ "tn"
+ ]
},
"flag_to": {
"unicode": "1F1F9-1F1F4",
@@ -6002,9 +12026,15 @@
"name": "tonga",
"shortname": ":flag_to:",
"category": "flags",
- "aliases": [":to:"],
+ "aliases": [
+ ":to:"
+ ],
"aliases_ascii": [],
- "keywords": ["country", "nation", "to"]
+ "keywords": [
+ "country",
+ "nation",
+ "to"
+ ]
},
"flag_tr": {
"unicode": "1F1F9-1F1F7",
@@ -6012,9 +12042,15 @@
"name": "turkey",
"shortname": ":flag_tr:",
"category": "flags",
- "aliases": [":tr:"],
+ "aliases": [
+ ":tr:"
+ ],
"aliases_ascii": [],
- "keywords": ["country", "nation", "turkiye"]
+ "keywords": [
+ "country",
+ "nation",
+ "turkiye"
+ ]
},
"flag_tt": {
"unicode": "1F1F9-1F1F9",
@@ -6022,9 +12058,15 @@
"name": "trinidad and tobago",
"shortname": ":flag_tt:",
"category": "flags",
- "aliases": [":tt:"],
+ "aliases": [
+ ":tt:"
+ ],
"aliases_ascii": [],
- "keywords": ["country", "nation", "tt"]
+ "keywords": [
+ "country",
+ "nation",
+ "tt"
+ ]
},
"flag_tv": {
"unicode": "1F1F9-1F1FB",
@@ -6032,9 +12074,15 @@
"name": "tuvalu",
"shortname": ":flag_tv:",
"category": "flags",
- "aliases": [":tuvalu:"],
+ "aliases": [
+ ":tuvalu:"
+ ],
"aliases_ascii": [],
- "keywords": ["country", "nation", "tv"]
+ "keywords": [
+ "country",
+ "nation",
+ "tv"
+ ]
},
"flag_tw": {
"unicode": "1F1F9-1F1FC",
@@ -6042,9 +12090,16 @@
"name": "the republic of china",
"shortname": ":flag_tw:",
"category": "flags",
- "aliases": [":tw:"],
+ "aliases": [
+ ":tw:"
+ ],
"aliases_ascii": [],
- "keywords": ["country", "nation", "taiwan", "tw"]
+ "keywords": [
+ "country",
+ "nation",
+ "taiwan",
+ "tw"
+ ]
},
"flag_tz": {
"unicode": "1F1F9-1F1FF",
@@ -6052,9 +12107,15 @@
"name": "tanzania",
"shortname": ":flag_tz:",
"category": "flags",
- "aliases": [":tz:"],
+ "aliases": [
+ ":tz:"
+ ],
"aliases_ascii": [],
- "keywords": ["country", "nation", "tz"]
+ "keywords": [
+ "country",
+ "nation",
+ "tz"
+ ]
},
"flag_ua": {
"unicode": "1F1FA-1F1E6",
@@ -6062,9 +12123,16 @@
"name": "ukraine",
"shortname": ":flag_ua:",
"category": "flags",
- "aliases": [":ua:"],
+ "aliases": [
+ ":ua:"
+ ],
"aliases_ascii": [],
- "keywords": ["country", "nation", "ukrayina", "ua"]
+ "keywords": [
+ "country",
+ "nation",
+ "ukrayina",
+ "ua"
+ ]
},
"flag_ug": {
"unicode": "1F1FA-1F1EC",
@@ -6072,9 +12140,27 @@
"name": "uganda",
"shortname": ":flag_ug:",
"category": "flags",
- "aliases": [":ug:"],
+ "aliases": [
+ ":ug:"
+ ],
+ "aliases_ascii": [],
+ "keywords": [
+ "country",
+ "nation",
+ "ug"
+ ]
+ },
+ "flag_um": {
+ "unicode": "1F1FA-1F1F2",
+ "unicode_alternates": "",
+ "name": "united states minor outlying islands",
+ "shortname": ":flag_um:",
+ "category": "flags",
+ "aliases": [
+ ":um:"
+ ],
"aliases_ascii": [],
- "keywords": ["country", "nation", "ug"]
+ "keywords": []
},
"flag_us": {
"unicode": "1F1FA-1F1F8",
@@ -6082,9 +12168,20 @@
"name": "united states",
"shortname": ":flag_us:",
"category": "flags",
- "aliases": [":us:"],
- "aliases_ascii": [],
- "keywords": ["american", "country", "nation", "usa", "united states of america", "america", "old glory", "us"]
+ "aliases": [
+ ":us:"
+ ],
+ "aliases_ascii": [],
+ "keywords": [
+ "american",
+ "country",
+ "nation",
+ "usa",
+ "united states of america",
+ "america",
+ "old glory",
+ "us"
+ ]
},
"flag_uy": {
"unicode": "1F1FA-1F1FE",
@@ -6092,9 +12189,15 @@
"name": "uruguay",
"shortname": ":flag_uy:",
"category": "flags",
- "aliases": [":uy:"],
+ "aliases": [
+ ":uy:"
+ ],
"aliases_ascii": [],
- "keywords": ["country", "nation", "uy"]
+ "keywords": [
+ "country",
+ "nation",
+ "uy"
+ ]
},
"flag_uz": {
"unicode": "1F1FA-1F1FF",
@@ -6102,9 +12205,16 @@
"name": "uzbekistan",
"shortname": ":flag_uz:",
"category": "flags",
- "aliases": [":uz:"],
+ "aliases": [
+ ":uz:"
+ ],
"aliases_ascii": [],
- "keywords": ["country", "nation", "uzbekiston respublikasi", "uz"]
+ "keywords": [
+ "country",
+ "nation",
+ "uzbekiston respublikasi",
+ "uz"
+ ]
},
"flag_va": {
"unicode": "1F1FB-1F1E6",
@@ -6112,9 +12222,15 @@
"name": "the vatican city",
"shortname": ":flag_va:",
"category": "flags",
- "aliases": [":va:"],
+ "aliases": [
+ ":va:"
+ ],
"aliases_ascii": [],
- "keywords": ["country", "nation", "va"]
+ "keywords": [
+ "country",
+ "nation",
+ "va"
+ ]
},
"flag_vc": {
"unicode": "1F1FB-1F1E8",
@@ -6122,9 +12238,15 @@
"name": "saint vincent and the grenadines",
"shortname": ":flag_vc:",
"category": "flags",
- "aliases": [":vc:"],
+ "aliases": [
+ ":vc:"
+ ],
"aliases_ascii": [],
- "keywords": ["country", "nation", "vc"]
+ "keywords": [
+ "country",
+ "nation",
+ "vc"
+ ]
},
"flag_ve": {
"unicode": "1F1FB-1F1EA",
@@ -6132,9 +12254,27 @@
"name": "venezuela",
"shortname": ":flag_ve:",
"category": "flags",
- "aliases": [":ve:"],
+ "aliases": [
+ ":ve:"
+ ],
+ "aliases_ascii": [],
+ "keywords": [
+ "country",
+ "nation",
+ "ve"
+ ]
+ },
+ "flag_vg": {
+ "unicode": "1F1FB-1F1EC",
+ "unicode_alternates": "",
+ "name": "british virgin islands",
+ "shortname": ":flag_vg:",
+ "category": "flags",
+ "aliases": [
+ ":vg:"
+ ],
"aliases_ascii": [],
- "keywords": ["country", "nation", "ve"]
+ "keywords": []
},
"flag_vi": {
"unicode": "1F1FB-1F1EE",
@@ -6142,9 +12282,15 @@
"name": "u.s. virgin islands",
"shortname": ":flag_vi:",
"category": "flags",
- "aliases": [":vi:"],
+ "aliases": [
+ ":vi:"
+ ],
"aliases_ascii": [],
- "keywords": ["country", "nation", "vi"]
+ "keywords": [
+ "country",
+ "nation",
+ "vi"
+ ]
},
"flag_vn": {
"unicode": "1F1FB-1F1F3",
@@ -6152,9 +12298,16 @@
"name": "vietnam",
"shortname": ":flag_vn:",
"category": "flags",
- "aliases": [":vn:"],
+ "aliases": [
+ ":vn:"
+ ],
"aliases_ascii": [],
- "keywords": ["country", "nation", "viet nam", "vn"]
+ "keywords": [
+ "country",
+ "nation",
+ "viet nam",
+ "vn"
+ ]
},
"flag_vu": {
"unicode": "1F1FB-1F1FA",
@@ -6162,9 +12315,15 @@
"name": "vanuatu",
"shortname": ":flag_vu:",
"category": "flags",
- "aliases": [":vu:"],
+ "aliases": [
+ ":vu:"
+ ],
"aliases_ascii": [],
- "keywords": ["country", "nation", "vu"]
+ "keywords": [
+ "country",
+ "nation",
+ "vu"
+ ]
},
"flag_wf": {
"unicode": "1F1FC-1F1EB",
@@ -6172,9 +12331,15 @@
"name": "wallis and futuna",
"shortname": ":flag_wf:",
"category": "flags",
- "aliases": [":wf:"],
+ "aliases": [
+ ":wf:"
+ ],
"aliases_ascii": [],
- "keywords": ["country", "nation", "wf"]
+ "keywords": [
+ "country",
+ "nation",
+ "wf"
+ ]
},
"flag_white": {
"unicode": "1F3F3",
@@ -6182,9 +12347,14 @@
"name": "waving white flag",
"shortname": ":flag_white:",
"category": "objects_symbols",
- "aliases": [":waving_white_flag:"],
+ "aliases": [
+ ":waving_white_flag:"
+ ],
"aliases_ascii": [],
- "keywords": ["symbol", "signal"]
+ "keywords": [
+ "symbol",
+ "signal"
+ ]
},
"flag_ws": {
"unicode": "1F1FC-1F1F8",
@@ -6192,9 +12362,16 @@
"name": "samoa",
"shortname": ":flag_ws:",
"category": "flags",
- "aliases": [":ws:"],
+ "aliases": [
+ ":ws:"
+ ],
"aliases_ascii": [],
- "keywords": ["country", "nation", "american samoa", "ws"]
+ "keywords": [
+ "country",
+ "nation",
+ "american samoa",
+ "ws"
+ ]
},
"flag_xk": {
"unicode": "1F1FD-1F1F0",
@@ -6202,9 +12379,15 @@
"name": "kosovo",
"shortname": ":flag_xk:",
"category": "flags",
- "aliases": [":xk:"],
+ "aliases": [
+ ":xk:"
+ ],
"aliases_ascii": [],
- "keywords": ["country", "nation", "xk"]
+ "keywords": [
+ "country",
+ "nation",
+ "xk"
+ ]
},
"flag_ye": {
"unicode": "1F1FE-1F1EA",
@@ -6212,9 +12395,28 @@
"name": "yemen",
"shortname": ":flag_ye:",
"category": "flags",
- "aliases": [":ye:"],
+ "aliases": [
+ ":ye:"
+ ],
+ "aliases_ascii": [],
+ "keywords": [
+ "country",
+ "nation",
+ "al yaman",
+ "ye"
+ ]
+ },
+ "flag_yt": {
+ "unicode": "1F1FE-1F1F9",
+ "unicode_alternates": "",
+ "name": "mayotte",
+ "shortname": ":flag_yt:",
+ "category": "flags",
+ "aliases": [
+ ":yt:"
+ ],
"aliases_ascii": [],
- "keywords": ["country", "nation", "al yaman", "ye"]
+ "keywords": []
},
"flag_za": {
"unicode": "1F1FF-1F1E6",
@@ -6222,9 +12424,14 @@
"name": "south africa",
"shortname": ":flag_za:",
"category": "flags",
- "aliases": [":za:"],
+ "aliases": [
+ ":za:"
+ ],
"aliases_ascii": [],
- "keywords": ["country", "nation"]
+ "keywords": [
+ "country",
+ "nation"
+ ]
},
"flag_zm": {
"unicode": "1F1FF-1F1F2",
@@ -6232,9 +12439,15 @@
"name": "zambia",
"shortname": ":flag_zm:",
"category": "flags",
- "aliases": [":zm:"],
+ "aliases": [
+ ":zm:"
+ ],
"aliases_ascii": [],
- "keywords": ["country", "nation", "zm"]
+ "keywords": [
+ "country",
+ "nation",
+ "zm"
+ ]
},
"flag_zw": {
"unicode": "1F1FF-1F1FC",
@@ -6242,9 +12455,15 @@
"name": "zimbabwe",
"shortname": ":flag_zw:",
"category": "flags",
- "aliases": [":zw:"],
+ "aliases": [
+ ":zw:"
+ ],
"aliases_ascii": [],
- "keywords": ["country", "nation", "zw"]
+ "keywords": [
+ "country",
+ "nation",
+ "zw"
+ ]
},
"flags": {
"unicode": "1F38F",
@@ -6254,7 +12473,23 @@
"category": "objects",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["banner", "carp", "fish", "japanese", "koinobori", "children", "kids", "boys", "celebration", "happiness", "carp", "streamers", "japanese", "holiday", "flags"],
+ "keywords": [
+ "banner",
+ "carp",
+ "fish",
+ "japanese",
+ "koinobori",
+ "children",
+ "kids",
+ "boys",
+ "celebration",
+ "happiness",
+ "carp",
+ "streamers",
+ "japanese",
+ "holiday",
+ "flags"
+ ],
"moji": "🎏"
},
"flashlight": {
@@ -6265,18 +12500,36 @@
"category": "objects",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["dark"],
+ "keywords": [
+ "dark"
+ ],
"moji": "🔦"
},
+ "fleur-de-lis": {
+ "unicode": "269C",
+ "unicode_alternates": "",
+ "name": "fleur-de-lis",
+ "shortname": ":fleur-de-lis:",
+ "category": "symbols",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": [
+ "symbol"
+ ]
+ },
"flip_phone": {
"unicode": "1F581",
"unicode_alternates": [],
"name": "clamshell mobile phone",
"shortname": ":flip_phone:",
"category": "objects_symbols",
- "aliases": [":clamshell_mobile_phone:"],
+ "aliases": [
+ ":clamshell_mobile_phone:"
+ ],
"aliases_ascii": [],
- "keywords": ["cellphone"]
+ "keywords": [
+ "cellphone"
+ ]
},
"floppy_black": {
"unicode": "1F5AA",
@@ -6284,9 +12537,20 @@
"name": "black hard shell floppy disk",
"shortname": ":floppy_black:",
"category": "objects_symbols",
- "aliases": [":black_hard_shell_floppy_disk:"],
- "aliases_ascii": [],
- "keywords": ["oldschool", "save", "technology", "storage", "information", "computer", "drive", "megabyte"]
+ "aliases": [
+ ":black_hard_shell_floppy_disk:"
+ ],
+ "aliases_ascii": [],
+ "keywords": [
+ "oldschool",
+ "save",
+ "technology",
+ "storage",
+ "information",
+ "computer",
+ "drive",
+ "megabyte"
+ ]
},
"floppy_disk": {
"unicode": "1F4BE",
@@ -6296,7 +12560,18 @@
"category": "objects",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["oldschool", "save", "technology", "floppy", "disk", "storage", "information", "computer", "drive", "megabyte"],
+ "keywords": [
+ "oldschool",
+ "save",
+ "technology",
+ "floppy",
+ "disk",
+ "storage",
+ "information",
+ "computer",
+ "drive",
+ "megabyte"
+ ],
"moji": "💾"
},
"floppy_white": {
@@ -6305,9 +12580,20 @@
"name": "white hard shell floppy disk",
"shortname": ":floppy_white:",
"category": "objects_symbols",
- "aliases": [":white_hard_shell_floppy_disk:"],
- "aliases_ascii": [],
- "keywords": ["oldschool", "save", "technology", "storage", "information", "computer", "drive", "megabyte"]
+ "aliases": [
+ ":white_hard_shell_floppy_disk:"
+ ],
+ "aliases_ascii": [],
+ "keywords": [
+ "oldschool",
+ "save",
+ "technology",
+ "storage",
+ "information",
+ "computer",
+ "drive",
+ "megabyte"
+ ]
},
"flower_playing_cards": {
"unicode": "1F3B4",
@@ -6317,7 +12603,15 @@
"category": "objects",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["playing", "card", "flower", "game", "august", "moon", "special"],
+ "keywords": [
+ "playing",
+ "card",
+ "flower",
+ "game",
+ "august",
+ "moon",
+ "special"
+ ],
"moji": "🎴"
},
"flushed": {
@@ -6327,8 +12621,21 @@
"shortname": ":flushed:",
"category": "emoticons",
"aliases": [],
- "aliases_ascii": [":$", "=$"],
- "keywords": ["blush", "face", "flattered", "flush", "blush", "red", "pink", "cheeks", "shy"],
+ "aliases_ascii": [
+ ":$",
+ "=$"
+ ],
+ "keywords": [
+ "blush",
+ "face",
+ "flattered",
+ "flush",
+ "blush",
+ "red",
+ "pink",
+ "cheeks",
+ "shy"
+ ],
"moji": "😳"
},
"fog": {
@@ -6339,7 +12646,12 @@
"category": "nature",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["weather", "damp", "cloud", "hazy"]
+ "keywords": [
+ "weather",
+ "damp",
+ "cloud",
+ "hazy"
+ ]
},
"foggy": {
"unicode": "1F301",
@@ -6349,7 +12661,14 @@
"category": "nature",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["mountain", "photo", "bridge", "weather", "fog", "foggy"],
+ "keywords": [
+ "mountain",
+ "photo",
+ "bridge",
+ "weather",
+ "fog",
+ "foggy"
+ ],
"moji": "🌁"
},
"folder": {
@@ -6360,7 +12679,9 @@
"category": "objects_symbols",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["documents"]
+ "keywords": [
+ "documents"
+ ]
},
"folder_open": {
"unicode": "1F5C1",
@@ -6368,9 +12689,14 @@
"name": "open folder",
"shortname": ":folder_open:",
"category": "objects_symbols",
- "aliases": [":open_folder:"],
+ "aliases": [
+ ":open_folder:"
+ ],
"aliases_ascii": [],
- "keywords": ["documents", "load"]
+ "keywords": [
+ "documents",
+ "load"
+ ]
},
"football": {
"unicode": "1F3C8",
@@ -6380,7 +12706,16 @@
"category": "objects",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["NFL", "balls", "sports", "football", "ball", "sport", "america", "american"],
+ "keywords": [
+ "NFL",
+ "balls",
+ "sports",
+ "football",
+ "ball",
+ "sport",
+ "america",
+ "american"
+ ],
"moji": "🏈"
},
"footprints": {
@@ -6391,7 +12726,9 @@
"category": "emoticons",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["feet"],
+ "keywords": [
+ "feet"
+ ],
"moji": "👣"
},
"fork_and_knife": {
@@ -6402,7 +12739,16 @@
"category": "objects",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["cutlery", "kitchen", "fork", "knife", "restaurant", "meal", "food", "eat"],
+ "keywords": [
+ "cutlery",
+ "kitchen",
+ "fork",
+ "knife",
+ "restaurant",
+ "meal",
+ "food",
+ "eat"
+ ],
"moji": "🍴"
},
"fork_knife_plate": {
@@ -6411,31 +12757,51 @@
"name": "fork and knife with plate",
"shortname": ":fork_knife_plate:",
"category": "travel_places",
- "aliases": [":fork_and_knife_with_plate:"],
- "aliases_ascii": [],
- "keywords": ["meal", "food", "breakfast", "lunch", "dinner", "utensils", "setting"]
+ "aliases": [
+ ":fork_and_knife_with_plate:"
+ ],
+ "aliases_ascii": [],
+ "keywords": [
+ "meal",
+ "food",
+ "breakfast",
+ "lunch",
+ "dinner",
+ "utensils",
+ "setting"
+ ]
},
"fountain": {
"unicode": "26F2",
- "unicode_alternates": ["26F2-FE0F"],
+ "unicode_alternates": [
+ "26F2-FE0F"
+ ],
"name": "fountain",
"shortname": ":fountain:",
"category": "places",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["photo"],
+ "keywords": [
+ "photo"
+ ],
"moji": "⛲"
},
"four": {
"moji": "4️⃣",
"unicode": "0034-20E3",
- "unicode_alternates": ["0034-FE0F-20E3"],
+ "unicode_alternates": [
+ "0034-FE0F-20E3"
+ ],
"name": "digit four",
"shortname": ":four:",
"category": "other",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["4", "blue-square", "numbers"]
+ "keywords": [
+ "4",
+ "blue-square",
+ "numbers"
+ ]
},
"four_leaf_clover": {
"unicode": "1F340",
@@ -6445,7 +12811,20 @@
"category": "nature",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["lucky", "nature", "plant", "vegetable", "clover", "four", "leaf", "luck", "irish", "saint", "patrick", "green"],
+ "keywords": [
+ "lucky",
+ "nature",
+ "plant",
+ "vegetable",
+ "clover",
+ "four",
+ "leaf",
+ "luck",
+ "irish",
+ "saint",
+ "patrick",
+ "green"
+ ],
"moji": "🍀"
},
"frame_photo": {
@@ -6454,9 +12833,13 @@
"name": "frame with picture",
"shortname": ":frame_photo:",
"category": "objects_symbols",
- "aliases": [":frame_with_picture:"],
+ "aliases": [
+ ":frame_with_picture:"
+ ],
"aliases_ascii": [],
- "keywords": ["photo"]
+ "keywords": [
+ "photo"
+ ]
},
"frame_tiles": {
"unicode": "1F5BD",
@@ -6464,9 +12847,14 @@
"name": "frame with tiles",
"shortname": ":frame_tiles:",
"category": "objects_symbols",
- "aliases": [":frame_with_tiles:"],
+ "aliases": [
+ ":frame_with_tiles:"
+ ],
"aliases_ascii": [],
- "keywords": ["photo", "painting"]
+ "keywords": [
+ "photo",
+ "painting"
+ ]
},
"frame_x": {
"unicode": "1F5BE",
@@ -6474,9 +12862,14 @@
"name": "frame with an x",
"shortname": ":frame_x:",
"category": "objects_symbols",
- "aliases": [":frame_with_an_x:"],
+ "aliases": [
+ ":frame_with_an_x:"
+ ],
"aliases_ascii": [],
- "keywords": ["photo", "painting"]
+ "keywords": [
+ "photo",
+ "painting"
+ ]
},
"free": {
"unicode": "1F193",
@@ -6486,7 +12879,10 @@
"category": "other",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["blue-square", "words"],
+ "keywords": [
+ "blue-square",
+ "words"
+ ],
"moji": "🆓"
},
"fried_shrimp": {
@@ -6497,7 +12893,15 @@
"category": "objects",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["animal", "food", "shrimp", "fried", "seafood", "small", "fish"],
+ "keywords": [
+ "animal",
+ "food",
+ "shrimp",
+ "fried",
+ "seafood",
+ "small",
+ "fish"
+ ],
"moji": "🍤"
},
"fries": {
@@ -6508,7 +12912,16 @@
"category": "objects",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["chips", "food", "fries", "french", "potato", "fry", "russet", "idaho"],
+ "keywords": [
+ "chips",
+ "food",
+ "fries",
+ "french",
+ "potato",
+ "fry",
+ "russet",
+ "idaho"
+ ],
"moji": "🍟"
},
"frog": {
@@ -6519,7 +12932,10 @@
"category": "nature",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["animal", "nature"],
+ "keywords": [
+ "animal",
+ "nature"
+ ],
"moji": "🐸"
},
"frowning": {
@@ -6528,20 +12944,50 @@
"name": "frowning face with open mouth",
"shortname": ":frowning:",
"category": "emoticons",
- "aliases": [":anguished:"],
- "aliases_ascii": [],
- "keywords": ["aw", "face", "frown", "sad", "pout", "sulk", "glower"],
+ "aliases": [
+ ":anguished:"
+ ],
+ "aliases_ascii": [],
+ "keywords": [
+ "aw",
+ "face",
+ "frown",
+ "sad",
+ "pout",
+ "sulk",
+ "glower"
+ ],
"moji": "😦"
},
+ "frowning2": {
+ "unicode": "2639",
+ "unicode_alternates": "",
+ "name": "white frowning face",
+ "shortname": ":frowning2:",
+ "category": "people",
+ "aliases": [
+ ":white_frowning_face:"
+ ],
+ "aliases_ascii": [],
+ "keywords": [
+ "frown",
+ "person"
+ ]
+ },
"fuelpump": {
"unicode": "26FD",
- "unicode_alternates": ["26FD-FE0F"],
+ "unicode_alternates": [
+ "26FD-FE0F"
+ ],
"name": "fuel pump",
"shortname": ":fuelpump:",
"category": "places",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["gas station", "petroleum"],
+ "keywords": [
+ "gas station",
+ "petroleum"
+ ],
"moji": "⛽"
},
"full_moon": {
@@ -6552,7 +12998,20 @@
"category": "nature",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["nature", "yellow", "moon", "full", "sky", "night", "cheese", "phase", "monster", "spooky", "werewolves", "twilight"],
+ "keywords": [
+ "nature",
+ "yellow",
+ "moon",
+ "full",
+ "sky",
+ "night",
+ "cheese",
+ "phase",
+ "monster",
+ "spooky",
+ "werewolves",
+ "twilight"
+ ],
"moji": "🌕"
},
"full_moon_with_face": {
@@ -6563,7 +13022,20 @@
"category": "nature",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["night", "moon", "full", "anthropomorphic", "face", "sky", "night", "cheese", "phase", "spooky", "werewolves", "monsters"],
+ "keywords": [
+ "night",
+ "moon",
+ "full",
+ "anthropomorphic",
+ "face",
+ "sky",
+ "night",
+ "cheese",
+ "phase",
+ "spooky",
+ "werewolves",
+ "monsters"
+ ],
"moji": "🌝"
},
"game_die": {
@@ -6574,9 +13046,30 @@
"category": "objects",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["dice", "game", "die", "dice", "craps", "gamble", "play"],
+ "keywords": [
+ "dice",
+ "game",
+ "die",
+ "dice",
+ "craps",
+ "gamble",
+ "play"
+ ],
"moji": "🎲"
},
+ "gear": {
+ "unicode": "2699",
+ "unicode_alternates": "",
+ "name": "gear",
+ "shortname": ":gear:",
+ "category": "objects",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": [
+ "object",
+ "tool"
+ ]
+ },
"gem": {
"unicode": "1F48E",
"unicode_alternates": [],
@@ -6585,18 +13078,35 @@
"category": "emoticons",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["blue", "ruby"],
+ "keywords": [
+ "blue",
+ "ruby"
+ ],
"moji": "💎"
},
"gemini": {
"unicode": "264A",
- "unicode_alternates": ["264A-FE0F"],
+ "unicode_alternates": [
+ "264A-FE0F"
+ ],
"name": "gemini",
"shortname": ":gemini:",
"category": "other",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["gemini", "twins", "astrology", "greek", "constellation", "stars", "zodiac", "sign", "sign", "zodiac", "horoscope"],
+ "keywords": [
+ "gemini",
+ "twins",
+ "astrology",
+ "greek",
+ "constellation",
+ "stars",
+ "zodiac",
+ "sign",
+ "sign",
+ "zodiac",
+ "horoscope"
+ ],
"moji": "♊"
},
"ghost": {
@@ -6607,7 +13117,9 @@
"category": "objects",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["halloween"],
+ "keywords": [
+ "halloween"
+ ],
"moji": "👻"
},
"gift": {
@@ -6618,7 +13130,18 @@
"category": "objects",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["birthday", "christmas", "present", "xmas", "gift", "present", "wrap", "package", "birthday", "wedding"],
+ "keywords": [
+ "birthday",
+ "christmas",
+ "present",
+ "xmas",
+ "gift",
+ "present",
+ "wrap",
+ "package",
+ "birthday",
+ "wedding"
+ ],
"moji": "🎁"
},
"gift_heart": {
@@ -6629,7 +13152,10 @@
"category": "objects",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["love", "valentines"],
+ "keywords": [
+ "love",
+ "valentines"
+ ],
"moji": "💝"
},
"girl": {
@@ -6640,9 +13166,82 @@
"category": "emoticons",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["female", "woman"],
+ "keywords": [
+ "female",
+ "woman"
+ ],
"moji": "👧"
},
+ "girl_tone1": {
+ "unicode": "1F467-1F3FB",
+ "unicode_alternates": "",
+ "name": "girl tone 1",
+ "shortname": ":girl_tone1:",
+ "category": "people",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": [
+ "female",
+ "kid",
+ "child"
+ ]
+ },
+ "girl_tone2": {
+ "unicode": "1F467-1F3FC",
+ "unicode_alternates": "",
+ "name": "girl tone 2",
+ "shortname": ":girl_tone2:",
+ "category": "people",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": [
+ "female",
+ "kid",
+ "child"
+ ]
+ },
+ "girl_tone3": {
+ "unicode": "1F467-1F3FD",
+ "unicode_alternates": "",
+ "name": "girl tone 3",
+ "shortname": ":girl_tone3:",
+ "category": "people",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": [
+ "female",
+ "kid",
+ "child"
+ ]
+ },
+ "girl_tone4": {
+ "unicode": "1F467-1F3FE",
+ "unicode_alternates": "",
+ "name": "girl tone 4",
+ "shortname": ":girl_tone4:",
+ "category": "people",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": [
+ "female",
+ "kid",
+ "child"
+ ]
+ },
+ "girl_tone5": {
+ "unicode": "1F467-1F3FF",
+ "unicode_alternates": "",
+ "name": "girl tone 5",
+ "shortname": ":girl_tone5:",
+ "category": "people",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": [
+ "female",
+ "kid",
+ "child"
+ ]
+ },
"girls_symbol": {
"unicode": "1F6CA",
"unicode_alternates": [],
@@ -6651,7 +13250,10 @@
"category": "objects_symbols",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["female", "child"]
+ "keywords": [
+ "female",
+ "child"
+ ]
},
"globe_with_meridians": {
"unicode": "1F310",
@@ -6661,7 +13263,17 @@
"category": "nature",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["earth", "international", "world", "earth", "meridian", "globe", "space", "planet", "home"],
+ "keywords": [
+ "earth",
+ "international",
+ "world",
+ "earth",
+ "meridian",
+ "globe",
+ "space",
+ "planet",
+ "home"
+ ],
"moji": "🌐"
},
"goat": {
@@ -6672,18 +13284,31 @@
"category": "nature",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["animal", "nature", "goat", "sheep", "kid", "billy", "livestock"],
+ "keywords": [
+ "animal",
+ "nature",
+ "goat",
+ "sheep",
+ "kid",
+ "billy",
+ "livestock"
+ ],
"moji": "🐐"
},
"golf": {
"unicode": "26F3",
- "unicode_alternates": ["26F3-FE0F"],
+ "unicode_alternates": [
+ "26F3-FE0F"
+ ],
"name": "flag in hole",
"shortname": ":golf:",
"category": "objects",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["business", "sports"],
+ "keywords": [
+ "business",
+ "sports"
+ ],
"moji": "⛳"
},
"golfer": {
@@ -6694,7 +13319,13 @@
"category": "activity",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["sport", "par", "birdie", "eagle", "mulligan"]
+ "keywords": [
+ "sport",
+ "par",
+ "birdie",
+ "eagle",
+ "mulligan"
+ ]
},
"grapes": {
"unicode": "1F347",
@@ -6704,7 +13335,16 @@
"category": "objects",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["food", "fruit", "grapes", "wine", "vinegar", "fruit", "cluster", "vine"],
+ "keywords": [
+ "food",
+ "fruit",
+ "grapes",
+ "wine",
+ "vinegar",
+ "fruit",
+ "cluster",
+ "vine"
+ ],
"moji": "🍇"
},
"green_apple": {
@@ -6715,7 +13355,17 @@
"category": "objects",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["fruit", "nature", "apple", "fruit", "green", "pie", "granny", "smith", "core"],
+ "keywords": [
+ "fruit",
+ "nature",
+ "apple",
+ "fruit",
+ "green",
+ "pie",
+ "granny",
+ "smith",
+ "core"
+ ],
"moji": "🍏"
},
"green_book": {
@@ -6726,7 +13376,11 @@
"category": "objects",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["knowledge", "library", "read"],
+ "keywords": [
+ "knowledge",
+ "library",
+ "read"
+ ],
"moji": "📗"
},
"green_heart": {
@@ -6737,7 +13391,22 @@
"category": "emoticons",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["affection", "like", "love", "valentines", "green", "heart", "love", "nature", "rebirth", "reborn", "jealous", "clingy", "envious", "possessive"],
+ "keywords": [
+ "affection",
+ "like",
+ "love",
+ "valentines",
+ "green",
+ "heart",
+ "love",
+ "nature",
+ "rebirth",
+ "reborn",
+ "jealous",
+ "clingy",
+ "envious",
+ "possessive"
+ ],
"moji": "💚"
},
"grey_exclamation": {
@@ -6748,7 +13417,9 @@
"category": "other",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["surprise"],
+ "keywords": [
+ "surprise"
+ ],
"moji": "❕"
},
"grey_question": {
@@ -6759,7 +13430,9 @@
"category": "other",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["doubts"],
+ "keywords": [
+ "doubts"
+ ],
"moji": "❔"
},
"grimacing": {
@@ -6770,7 +13443,14 @@
"category": "emoticons",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["face", "grimace", "teeth", "grimace", "disapprove", "pain"],
+ "keywords": [
+ "face",
+ "grimace",
+ "teeth",
+ "grimace",
+ "disapprove",
+ "pain"
+ ],
"moji": "😬"
},
"grin": {
@@ -6781,7 +13461,17 @@
"category": "emoticons",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["face", "happy", "joy", "smile", "grin", "grinning", "smiling", "smile", "smiley"],
+ "keywords": [
+ "face",
+ "happy",
+ "joy",
+ "smile",
+ "grin",
+ "grinning",
+ "smiling",
+ "smile",
+ "smiley"
+ ],
"moji": "😁"
},
"grinning": {
@@ -6792,7 +13482,17 @@
"category": "emoticons",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["face", "happy", "joy", "smile", "grin", "grinning", "smiling", "smile", "smiley"],
+ "keywords": [
+ "face",
+ "happy",
+ "joy",
+ "smile",
+ "grin",
+ "grinning",
+ "smiling",
+ "smile",
+ "smiley"
+ ],
"moji": "🕧"
},
"guardsman": {
@@ -6803,9 +13503,138 @@
"category": "emoticons",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["british", "gb", "male", "man", "uk", "guardsman", "guard", "bearskin", "hat", "british", "queen", "ceremonial", "military"],
+ "keywords": [
+ "british",
+ "gb",
+ "male",
+ "man",
+ "uk",
+ "guardsman",
+ "guard",
+ "bearskin",
+ "hat",
+ "british",
+ "queen",
+ "ceremonial",
+ "military"
+ ],
"moji": "💂"
},
+ "guardsman_tone1": {
+ "unicode": "1F482-1F3FB",
+ "unicode_alternates": "",
+ "name": "guardsman tone 1",
+ "shortname": ":guardsman_tone1:",
+ "category": "people",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": [
+ "british",
+ "gb",
+ "male",
+ "man",
+ "uk",
+ "guard",
+ "bearskin",
+ "hat",
+ "british",
+ "queen",
+ "ceremonial",
+ "military"
+ ]
+ },
+ "guardsman_tone2": {
+ "unicode": "1F482-1F3FC",
+ "unicode_alternates": "",
+ "name": "guardsman tone 2",
+ "shortname": ":guardsman_tone2:",
+ "category": "people",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": [
+ "british",
+ "gb",
+ "male",
+ "man",
+ "uk",
+ "guard",
+ "bearskin",
+ "hat",
+ "british",
+ "queen",
+ "ceremonial",
+ "military"
+ ]
+ },
+ "guardsman_tone3": {
+ "unicode": "1F482-1F3FD",
+ "unicode_alternates": "",
+ "name": "guardsman tone 3",
+ "shortname": ":guardsman_tone3:",
+ "category": "people",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": [
+ "british",
+ "gb",
+ "male",
+ "man",
+ "uk",
+ "guard",
+ "bearskin",
+ "hat",
+ "british",
+ "queen",
+ "ceremonial",
+ "military"
+ ]
+ },
+ "guardsman_tone4": {
+ "unicode": "1F482-1F3FE",
+ "unicode_alternates": "",
+ "name": "guardsman tone 4",
+ "shortname": ":guardsman_tone4:",
+ "category": "people",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": [
+ "british",
+ "gb",
+ "male",
+ "man",
+ "uk",
+ "guard",
+ "bearskin",
+ "hat",
+ "british",
+ "queen",
+ "ceremonial",
+ "military"
+ ]
+ },
+ "guardsman_tone5": {
+ "unicode": "1F482-1F3FF",
+ "unicode_alternates": "",
+ "name": "guardsman tone 5",
+ "shortname": ":guardsman_tone5:",
+ "category": "people",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": [
+ "british",
+ "gb",
+ "male",
+ "man",
+ "uk",
+ "guard",
+ "bearskin",
+ "hat",
+ "british",
+ "queen",
+ "ceremonial",
+ "military"
+ ]
+ },
"guitar": {
"unicode": "1F3B8",
"unicode_alternates": [],
@@ -6814,7 +13643,18 @@
"category": "objects",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["instrument", "music", "guitar", "string", "music", "instrument", "jam", "rock", "acoustic", "electric"],
+ "keywords": [
+ "instrument",
+ "music",
+ "guitar",
+ "string",
+ "music",
+ "instrument",
+ "jam",
+ "rock",
+ "acoustic",
+ "electric"
+ ],
"moji": "🎸"
},
"gun": {
@@ -6825,7 +13665,10 @@
"category": "objects",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["violence", "weapon"],
+ "keywords": [
+ "violence",
+ "weapon"
+ ],
"moji": "🔫"
},
"haircut": {
@@ -6836,9 +13679,83 @@
"category": "emoticons",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["female", "girl", "woman"],
+ "keywords": [
+ "female",
+ "girl",
+ "woman"
+ ],
"moji": "💇"
},
+ "haircut_tone1": {
+ "unicode": "1F487-1F3FB",
+ "unicode_alternates": "",
+ "name": "haircut tone 1",
+ "shortname": ":haircut_tone1:",
+ "category": "people",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": [
+ "female",
+ "girl",
+ "woman"
+ ]
+ },
+ "haircut_tone2": {
+ "unicode": "1F487-1F3FC",
+ "unicode_alternates": "",
+ "name": "haircut tone 2",
+ "shortname": ":haircut_tone2:",
+ "category": "people",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": [
+ "female",
+ "girl",
+ "woman"
+ ]
+ },
+ "haircut_tone3": {
+ "unicode": "1F487-1F3FD",
+ "unicode_alternates": "",
+ "name": "haircut tone 3",
+ "shortname": ":haircut_tone3:",
+ "category": "people",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": [
+ "female",
+ "girl",
+ "woman"
+ ]
+ },
+ "haircut_tone4": {
+ "unicode": "1F487-1F3FE",
+ "unicode_alternates": "",
+ "name": "haircut tone 4",
+ "shortname": ":haircut_tone4:",
+ "category": "people",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": [
+ "female",
+ "girl",
+ "woman"
+ ]
+ },
+ "haircut_tone5": {
+ "unicode": "1F487-1F3FF",
+ "unicode_alternates": "",
+ "name": "haircut tone 5",
+ "shortname": ":haircut_tone5:",
+ "category": "people",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": [
+ "female",
+ "girl",
+ "woman"
+ ]
+ },
"hamburger": {
"unicode": "1F354",
"unicode_alternates": [],
@@ -6847,7 +13764,15 @@
"category": "objects",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["food", "meat", "hamburger", "burger", "meat", "cow", "beef"],
+ "keywords": [
+ "food",
+ "meat",
+ "hamburger",
+ "burger",
+ "meat",
+ "cow",
+ "beef"
+ ],
"moji": "🍔"
},
"hammer": {
@@ -6858,9 +13783,31 @@
"category": "objects",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["done", "judge", "law", "ruling", "tools", "verdict"],
+ "keywords": [
+ "done",
+ "judge",
+ "law",
+ "ruling",
+ "tools",
+ "verdict"
+ ],
"moji": "🔨"
},
+ "hammer_pick": {
+ "unicode": "2692",
+ "unicode_alternates": "",
+ "name": "hammer and pick",
+ "shortname": ":hammer_pick:",
+ "category": "objects",
+ "aliases": [
+ ":hammer_and_pick:"
+ ],
+ "aliases_ascii": [],
+ "keywords": [
+ "object",
+ "tool"
+ ]
+ },
"hamster": {
"unicode": "1F439",
"unicode_alternates": [],
@@ -6869,7 +13816,10 @@
"category": "nature",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["animal", "nature"],
+ "keywords": [
+ "animal",
+ "nature"
+ ],
"moji": "🐹"
},
"hand_splayed": {
@@ -6878,9 +13828,16 @@
"name": "raised hand with fingers splayed",
"shortname": ":hand_splayed:",
"category": "people",
- "aliases": [":raised_hand_with_fingers_splayed:"],
+ "aliases": [
+ ":raised_hand_with_fingers_splayed:"
+ ],
"aliases_ascii": [],
- "keywords": ["hi", "five", "stop", "halt"]
+ "keywords": [
+ "hi",
+ "five",
+ "stop",
+ "halt"
+ ]
},
"hand_splayed_reverse": {
"unicode": "1F591",
@@ -6888,9 +13845,101 @@
"name": "reversed raised hand with fingers splayed",
"shortname": ":hand_splayed_reverse:",
"category": "people",
- "aliases": [":reversed_raised_hand_with_fingers_splayed:"],
+ "aliases": [
+ ":reversed_raised_hand_with_fingers_splayed:"
+ ],
+ "aliases_ascii": [],
+ "keywords": [
+ "hi",
+ "five",
+ "stop",
+ "halt"
+ ]
+ },
+ "hand_splayed_tone1": {
+ "unicode": "1F590-1F3FB",
+ "unicode_alternates": "",
+ "name": "raised hand with fingers splayed tone 1",
+ "shortname": ":hand_splayed_tone1:",
+ "category": "people",
+ "aliases": [
+ ":raised_hand_with_fingers_splayed_tone1:"
+ ],
+ "aliases_ascii": [],
+ "keywords": [
+ "hi",
+ "five",
+ "stop",
+ "halt"
+ ]
+ },
+ "hand_splayed_tone2": {
+ "unicode": "1F590-1F3FC",
+ "unicode_alternates": "",
+ "name": "raised hand with fingers splayed tone 2",
+ "shortname": ":hand_splayed_tone2:",
+ "category": "people",
+ "aliases": [
+ ":raised_hand_with_fingers_splayed_tone2:"
+ ],
+ "aliases_ascii": [],
+ "keywords": [
+ "hi",
+ "five",
+ "stop",
+ "halt"
+ ]
+ },
+ "hand_splayed_tone3": {
+ "unicode": "1F590-1F3FD",
+ "unicode_alternates": "",
+ "name": "raised hand with fingers splayed tone 3",
+ "shortname": ":hand_splayed_tone3:",
+ "category": "people",
+ "aliases": [
+ ":raised_hand_with_fingers_splayed_tone3:"
+ ],
+ "aliases_ascii": [],
+ "keywords": [
+ "hi",
+ "five",
+ "stop",
+ "halt"
+ ]
+ },
+ "hand_splayed_tone4": {
+ "unicode": "1F590-1F3FE",
+ "unicode_alternates": "",
+ "name": "raised hand with fingers splayed tone 4",
+ "shortname": ":hand_splayed_tone4:",
+ "category": "people",
+ "aliases": [
+ ":raised_hand_with_fingers_splayed_tone4:"
+ ],
+ "aliases_ascii": [],
+ "keywords": [
+ "hi",
+ "five",
+ "stop",
+ "halt"
+ ]
+ },
+ "hand_splayed_tone5": {
+ "unicode": "1F590-1F3FF",
+ "unicode_alternates": "",
+ "name": "raised hand with fingers splayed tone 5",
+ "shortname": ":hand_splayed_tone5:",
+ "category": "people",
+ "aliases": [
+ ":raised_hand_with_fingers_splayed_tone5:"
+ ],
"aliases_ascii": [],
- "keywords": ["hi", "five", "stop", "halt"]
+ "keywords": [
+ "hi",
+ "five",
+ "stop",
+ "halt"
+ ]
},
"hand_victory": {
"unicode": "1F594",
@@ -6898,9 +13947,13 @@
"name": "reversed victory hand",
"shortname": ":hand_victory:",
"category": "people",
- "aliases": [":reversed_victory_hand:"],
+ "aliases": [
+ ":reversed_victory_hand:"
+ ],
"aliases_ascii": [],
- "keywords": ["fu"]
+ "keywords": [
+ "fu"
+ ]
},
"handbag": {
"unicode": "1F45C",
@@ -6910,7 +13963,12 @@
"category": "emoticons",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["accessories", "accessory", "bag", "fashion"],
+ "keywords": [
+ "accessories",
+ "accessory",
+ "bag",
+ "fashion"
+ ],
"moji": "👜"
},
"hard_disk": {
@@ -6921,18 +13979,32 @@
"category": "objects_symbols",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["save", "technology", "storage", "information", "computer", "drive", "megabyte", "gigabyte", "hd"]
+ "keywords": [
+ "save",
+ "technology",
+ "storage",
+ "information",
+ "computer",
+ "drive",
+ "megabyte",
+ "gigabyte",
+ "hd"
+ ]
},
"hash": {
"moji": "#⃣",
"unicode": "0023-20E3",
- "unicode_alternates": ["0023-FE0F-20E3"],
+ "unicode_alternates": [
+ "0023-FE0F-20E3"
+ ],
"name": "number sign",
"shortname": ":hash:",
"category": "other",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["symbol"]
+ "keywords": [
+ "symbol"
+ ]
},
"hatched_chick": {
"unicode": "1F425",
@@ -6942,7 +14014,17 @@
"category": "nature",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["baby", "chicken", "chick", "baby", "bird", "chicken", "young", "woman", "cute"],
+ "keywords": [
+ "baby",
+ "chicken",
+ "chick",
+ "baby",
+ "bird",
+ "chicken",
+ "young",
+ "woman",
+ "cute"
+ ],
"moji": "🐥"
},
"hatching_chick": {
@@ -6953,9 +14035,33 @@
"category": "nature",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["born", "chicken", "egg", "chick", "egg", "baby", "bird", "chicken", "young", "woman", "cute"],
+ "keywords": [
+ "born",
+ "chicken",
+ "egg",
+ "chick",
+ "egg",
+ "baby",
+ "bird",
+ "chicken",
+ "young",
+ "woman",
+ "cute"
+ ],
"moji": "🐣"
},
+ "head_bandage": {
+ "unicode": "1F915",
+ "unicode_alternates": "",
+ "name": "face with head-bandage",
+ "shortname": ":head_bandage:",
+ "category": "people",
+ "aliases": [
+ ":face_with_head_bandage:"
+ ],
+ "aliases_ascii": [],
+ "keywords": []
+ },
"headphones": {
"unicode": "1F3A7",
"unicode_alternates": [],
@@ -6964,7 +14070,19 @@
"category": "objects",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["gadgets", "music", "score", "headphone", "sound", "music", "ears", "beats", "buds", "audio", "listen"],
+ "keywords": [
+ "gadgets",
+ "music",
+ "score",
+ "headphone",
+ "sound",
+ "music",
+ "ears",
+ "beats",
+ "buds",
+ "audio",
+ "listen"
+ ],
"moji": "🎧"
},
"hear_no_evil": {
@@ -6975,19 +14093,47 @@
"category": "emoticons",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["animal", "monkey", "monkey", "ears", "hear", "sound", "kikazaru"],
+ "keywords": [
+ "animal",
+ "monkey",
+ "monkey",
+ "ears",
+ "hear",
+ "sound",
+ "kikazaru"
+ ],
"moji": "🙉"
},
"heart": {
"moji": "❤",
"unicode": "2764",
- "unicode_alternates": ["2764-FE0F"],
+ "unicode_alternates": [
+ "2764-FE0F"
+ ],
"name": "heavy black heart",
"shortname": ":heart:",
"category": "emoticons",
"aliases": [],
- "aliases_ascii": ["<3"],
- "keywords": ["like", "love", "red", "pink", "black", "heart", "love", "passion", "romance", "intense", "desire", "death", "evil", "cold", "valentines"]
+ "aliases_ascii": [
+ "<3"
+ ],
+ "keywords": [
+ "like",
+ "love",
+ "red",
+ "pink",
+ "black",
+ "heart",
+ "love",
+ "passion",
+ "romance",
+ "intense",
+ "desire",
+ "death",
+ "evil",
+ "cold",
+ "valentines"
+ ]
},
"heart_decoration": {
"unicode": "1F49F",
@@ -6997,9 +14143,29 @@
"category": "other",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["like", "love", "purple-square"],
+ "keywords": [
+ "like",
+ "love",
+ "purple-square"
+ ],
"moji": "💟"
},
+ "heart_exclamation": {
+ "unicode": "2763",
+ "unicode_alternates": "",
+ "name": "heavy heart exclamation mark ornament",
+ "shortname": ":heart_exclamation:",
+ "category": "symbols",
+ "aliases": [
+ ":heavy_heart_exclamation_mark_ornament:"
+ ],
+ "aliases_ascii": [],
+ "keywords": [
+ "emotion",
+ "punctuation",
+ "symbol"
+ ]
+ },
"heart_eyes": {
"unicode": "1F60D",
"unicode_alternates": [],
@@ -7008,7 +14174,22 @@
"category": "emoticons",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["affection", "crush", "face", "infatuation", "like", "love", "valentines", "smiling", "heart", "lovestruck", "love", "flirt", "smile", "heart-shaped"],
+ "keywords": [
+ "affection",
+ "crush",
+ "face",
+ "infatuation",
+ "like",
+ "love",
+ "valentines",
+ "smiling",
+ "heart",
+ "lovestruck",
+ "love",
+ "flirt",
+ "smile",
+ "heart-shaped"
+ ],
"moji": "😍"
},
"heart_eyes_cat": {
@@ -7019,7 +14200,17 @@
"category": "emoticons",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["affection", "animal", "cats", "like", "love", "valentines", "lovestruck", "love", "heart"],
+ "keywords": [
+ "affection",
+ "animal",
+ "cats",
+ "like",
+ "love",
+ "valentines",
+ "lovestruck",
+ "love",
+ "heart"
+ ],
"moji": "😻"
},
"heart_tip": {
@@ -7028,9 +14219,16 @@
"name": "heart with tip on the left",
"shortname": ":heart_tip:",
"category": "celebration",
- "aliases": [":heart_with_tip_on_the_left:"],
+ "aliases": [
+ ":heart_with_tip_on_the_left:"
+ ],
"aliases_ascii": [],
- "keywords": ["affection", "like", "love", "valentines"]
+ "keywords": [
+ "affection",
+ "like",
+ "love",
+ "valentines"
+ ]
},
"heartbeat": {
"unicode": "1F493",
@@ -7040,7 +14238,12 @@
"category": "emoticons",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["affection", "like", "love", "valentines"],
+ "keywords": [
+ "affection",
+ "like",
+ "love",
+ "valentines"
+ ],
"moji": "💓"
},
"heartpulse": {
@@ -7051,29 +14254,44 @@
"category": "emoticons",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["affection", "like", "love", "valentines"],
+ "keywords": [
+ "affection",
+ "like",
+ "love",
+ "valentines"
+ ],
"moji": "💗"
},
"hearts": {
"unicode": "2665",
- "unicode_alternates": ["2665-FE0F"],
+ "unicode_alternates": [
+ "2665-FE0F"
+ ],
"name": "black heart suit",
"shortname": ":hearts:",
"category": "other",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["cards", "poker"],
+ "keywords": [
+ "cards",
+ "poker"
+ ],
"moji": "♥"
},
"heavy_check_mark": {
"unicode": "2714",
- "unicode_alternates": ["2714-FE0F"],
+ "unicode_alternates": [
+ "2714-FE0F"
+ ],
"name": "heavy check mark",
"shortname": ":heavy_check_mark:",
"category": "other",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["nike", "ok"],
+ "keywords": [
+ "nike",
+ "ok"
+ ],
"moji": "✔"
},
"heavy_division_sign": {
@@ -7084,7 +14302,11 @@
"category": "other",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["calculation", "divide", "math"],
+ "keywords": [
+ "calculation",
+ "divide",
+ "math"
+ ],
"moji": "➗"
},
"heavy_dollar_sign": {
@@ -7095,7 +14317,18 @@
"category": "other",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["currency", "money", "payment", "dollar", "currency", "money", "cash", "sale", "purchase", "value"],
+ "keywords": [
+ "currency",
+ "money",
+ "payment",
+ "dollar",
+ "currency",
+ "money",
+ "cash",
+ "sale",
+ "purchase",
+ "value"
+ ],
"moji": "💲"
},
"heavy_minus_sign": {
@@ -7106,18 +14339,26 @@
"category": "other",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["calculation", "math"],
+ "keywords": [
+ "calculation",
+ "math"
+ ],
"moji": "➖"
},
"heavy_multiplication_x": {
"unicode": "2716",
- "unicode_alternates": ["2716-FE0F"],
+ "unicode_alternates": [
+ "2716-FE0F"
+ ],
"name": "heavy multiplication x",
"shortname": ":heavy_multiplication_x:",
"category": "other",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["calculation", "math"],
+ "keywords": [
+ "calculation",
+ "math"
+ ],
"moji": "✖"
},
"heavy_plus_sign": {
@@ -7128,7 +14369,10 @@
"category": "other",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["calculation", "math"],
+ "keywords": [
+ "calculation",
+ "math"
+ ],
"moji": "➕"
},
"helicopter": {
@@ -7139,9 +14383,33 @@
"category": "places",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["transportation", "vehicle", "helicopter", "helo", "gyro", "gyrocopter"],
+ "keywords": [
+ "transportation",
+ "vehicle",
+ "helicopter",
+ "helo",
+ "gyro",
+ "gyrocopter"
+ ],
"moji": "🚁"
},
+ "helmet_with_cross": {
+ "unicode": "26D1",
+ "unicode_alternates": "",
+ "name": "helmet with white cross",
+ "shortname": ":helmet_with_cross:",
+ "category": "people",
+ "aliases": [
+ ":helmet_with_white_cross:"
+ ],
+ "aliases_ascii": [],
+ "keywords": [
+ "aid",
+ "face",
+ "hat",
+ "person"
+ ]
+ },
"herb": {
"unicode": "1F33F",
"unicode_alternates": [],
@@ -7150,7 +14418,19 @@
"category": "nature",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["grass", "lawn", "medicine", "plant", "vegetable", "weed", "herb", "spice", "plant", "cook", "cooking"],
+ "keywords": [
+ "grass",
+ "lawn",
+ "medicine",
+ "plant",
+ "vegetable",
+ "weed",
+ "herb",
+ "spice",
+ "plant",
+ "cook",
+ "cooking"
+ ],
"moji": "🌿"
},
"hibiscus": {
@@ -7161,7 +14441,14 @@
"category": "nature",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["flowers", "plant", "vegetable", "hibiscus", "flower", "warm"],
+ "keywords": [
+ "flowers",
+ "plant",
+ "vegetable",
+ "hibiscus",
+ "flower",
+ "warm"
+ ],
"moji": "🌺"
},
"high_brightness": {
@@ -7172,7 +14459,11 @@
"category": "objects",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["light", "summer", "sun"],
+ "keywords": [
+ "light",
+ "summer",
+ "sun"
+ ],
"moji": "🔆"
},
"high_heel": {
@@ -7183,9 +14474,23 @@
"category": "emoticons",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["fashion", "female", "shoes"],
+ "keywords": [
+ "fashion",
+ "female",
+ "shoes"
+ ],
"moji": "👠"
},
+ "hockey": {
+ "unicode": "1F3D2",
+ "unicode_alternates": "",
+ "name": "ice hockey stick and puck",
+ "shortname": ":hockey:",
+ "category": "activity",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": []
+ },
"hole": {
"unicode": "1F573",
"unicode_alternates": [],
@@ -7194,7 +14499,10 @@
"category": "objects_symbols",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["pit", "well"]
+ "keywords": [
+ "pit",
+ "well"
+ ]
},
"homes": {
"unicode": "1F3D8",
@@ -7202,9 +14510,19 @@
"name": "house buildings",
"shortname": ":homes:",
"category": "travel_places",
- "aliases": [":house_buildings:"],
- "aliases_ascii": [],
- "keywords": ["home", "residence", "dwelling", "mansion", "bungalow", "ranch", "craftsman"]
+ "aliases": [
+ ":house_buildings:"
+ ],
+ "aliases_ascii": [],
+ "keywords": [
+ "home",
+ "residence",
+ "dwelling",
+ "mansion",
+ "bungalow",
+ "ranch",
+ "craftsman"
+ ]
},
"honey_pot": {
"unicode": "1F36F",
@@ -7214,7 +14532,15 @@
"category": "objects",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["bees", "sweet", "honey", "pot", "bees", "pooh", "bear"],
+ "keywords": [
+ "bees",
+ "sweet",
+ "honey",
+ "pot",
+ "bees",
+ "pooh",
+ "bear"
+ ],
"moji": "🍯"
},
"horse": {
@@ -7225,7 +14551,10 @@
"category": "nature",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["animal", "brown"],
+ "keywords": [
+ "animal",
+ "brown"
+ ],
"moji": "🐴"
},
"horse_racing": {
@@ -7236,9 +14565,103 @@
"category": "objects",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["animal", "betting", "competition", "horse", "race", "racing", "jockey", "triple crown"],
+ "keywords": [
+ "animal",
+ "betting",
+ "competition",
+ "horse",
+ "race",
+ "racing",
+ "jockey",
+ "triple crown"
+ ],
"moji": "🏇"
},
+ "horse_racing_tone1": {
+ "unicode": "1F3C7-1F3FB",
+ "unicode_alternates": "",
+ "name": "horse racing tone 1",
+ "shortname": ":horse_racing_tone1:",
+ "category": "activity",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": [
+ "animal",
+ "betting",
+ "competition",
+ "race",
+ "jockey",
+ "triple crown"
+ ]
+ },
+ "horse_racing_tone2": {
+ "unicode": "1F3C7-1F3FC",
+ "unicode_alternates": "",
+ "name": "horse racing tone 2",
+ "shortname": ":horse_racing_tone2:",
+ "category": "activity",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": [
+ "animal",
+ "betting",
+ "competition",
+ "race",
+ "jockey",
+ "triple crown"
+ ]
+ },
+ "horse_racing_tone3": {
+ "unicode": "1F3C7-1F3FD",
+ "unicode_alternates": "",
+ "name": "horse racing tone 3",
+ "shortname": ":horse_racing_tone3:",
+ "category": "activity",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": [
+ "animal",
+ "betting",
+ "competition",
+ "race",
+ "jockey",
+ "triple crown"
+ ]
+ },
+ "horse_racing_tone4": {
+ "unicode": "1F3C7-1F3FE",
+ "unicode_alternates": "",
+ "name": "horse racing tone 4",
+ "shortname": ":horse_racing_tone4:",
+ "category": "activity",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": [
+ "animal",
+ "betting",
+ "competition",
+ "race",
+ "jockey",
+ "triple crown"
+ ]
+ },
+ "horse_racing_tone5": {
+ "unicode": "1F3C7-1F3FF",
+ "unicode_alternates": "",
+ "name": "horse racing tone 5",
+ "shortname": ":horse_racing_tone5:",
+ "category": "activity",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": [
+ "animal",
+ "betting",
+ "competition",
+ "race",
+ "jockey",
+ "triple crown"
+ ]
+ },
"hospital": {
"unicode": "1F3E5",
"unicode_alternates": [],
@@ -7247,7 +14670,12 @@
"category": "places",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["building", "doctor", "health", "surgery"],
+ "keywords": [
+ "building",
+ "doctor",
+ "health",
+ "surgery"
+ ],
"moji": "🏥"
},
"hot_pepper": {
@@ -7258,7 +14686,27 @@
"category": "food_drink",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["food", "nature", "spicy", "chili", "cayenne", "habanero", "jalapeno"]
+ "keywords": [
+ "food",
+ "nature",
+ "spicy",
+ "chili",
+ "cayenne",
+ "habanero",
+ "jalapeno"
+ ]
+ },
+ "hotdog": {
+ "unicode": "1F32D",
+ "unicode_alternates": "",
+ "name": "hot dog",
+ "shortname": ":hotdog:",
+ "category": "foods",
+ "aliases": [
+ ":hot_dog:"
+ ],
+ "aliases_ascii": [],
+ "keywords": []
},
"hotel": {
"unicode": "1F3E8",
@@ -7268,29 +14716,50 @@
"category": "places",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["accomodation", "building", "checkin", "whotel", "hotel", "motel", "holiday inn", "hospital"],
+ "keywords": [
+ "accomodation",
+ "building",
+ "checkin",
+ "whotel",
+ "hotel",
+ "motel",
+ "holiday inn",
+ "hospital"
+ ],
"moji": "🏨"
},
"hotsprings": {
"unicode": "2668",
- "unicode_alternates": ["2668-FE0F"],
+ "unicode_alternates": [
+ "2668-FE0F"
+ ],
"name": "hot springs",
"shortname": ":hotsprings:",
"category": "places",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["bath", "relax", "warm"],
+ "keywords": [
+ "bath",
+ "relax",
+ "warm"
+ ],
"moji": "♨"
},
"hourglass": {
"unicode": "231B",
- "unicode_alternates": ["231B-FE0F"],
+ "unicode_alternates": [
+ "231B-FE0F"
+ ],
"name": "hourglass",
"shortname": ":hourglass:",
"category": "objects",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["clock", "oldschool", "time"],
+ "keywords": [
+ "clock",
+ "oldschool",
+ "time"
+ ],
"moji": "⌛"
},
"hourglass_flowing_sand": {
@@ -7301,7 +14770,11 @@
"category": "objects",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["countdown", "oldschool", "time"],
+ "keywords": [
+ "countdown",
+ "oldschool",
+ "time"
+ ],
"moji": "⏳"
},
"house": {
@@ -7312,7 +14785,18 @@
"category": "places",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["building", "home", "house", "home", "residence", "dwelling", "mansion", "bungalow", "ranch", "craftsman"],
+ "keywords": [
+ "building",
+ "home",
+ "house",
+ "home",
+ "residence",
+ "dwelling",
+ "mansion",
+ "bungalow",
+ "ranch",
+ "craftsman"
+ ],
"moji": "🏠"
},
"house_abandoned": {
@@ -7321,9 +14805,24 @@
"name": "derelict house building",
"shortname": ":house_abandoned:",
"category": "travel_places",
- "aliases": [":derelict_house_building:"],
- "aliases_ascii": [],
- "keywords": ["home", "residence", "dwelling", "mansion", "bungalow", "ranch", "craftsman", "boarded", "abandoned", "vacant", "run down", "shoddy"]
+ "aliases": [
+ ":derelict_house_building:"
+ ],
+ "aliases_ascii": [],
+ "keywords": [
+ "home",
+ "residence",
+ "dwelling",
+ "mansion",
+ "bungalow",
+ "ranch",
+ "craftsman",
+ "boarded",
+ "abandoned",
+ "vacant",
+ "run down",
+ "shoddy"
+ ]
},
"house_with_garden": {
"unicode": "1F3E1",
@@ -7333,9 +14832,25 @@
"category": "places",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["home", "nature", "plant"],
+ "keywords": [
+ "home",
+ "nature",
+ "plant"
+ ],
"moji": "🏡"
},
+ "hugging": {
+ "unicode": "1F917",
+ "unicode_alternates": "",
+ "name": "hugging face",
+ "shortname": ":hugging:",
+ "category": "people",
+ "aliases": [
+ ":hugging_face:"
+ ],
+ "aliases_ascii": [],
+ "keywords": []
+ },
"hushed": {
"unicode": "1F62F",
"unicode_alternates": [],
@@ -7344,7 +14859,14 @@
"category": "emoticons",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["face", "woo", "quiet", "hush", "whisper", "silent"],
+ "keywords": [
+ "face",
+ "woo",
+ "quiet",
+ "hush",
+ "whisper",
+ "silent"
+ ],
"moji": "😯"
},
"ice_cream": {
@@ -7355,9 +14877,37 @@
"category": "objects",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["desert", "food", "hot", "icecream", "ice", "cream", "dairy", "dessert", "cold", "soft", "serve", "cone", "waffle"],
+ "keywords": [
+ "desert",
+ "food",
+ "hot",
+ "icecream",
+ "ice",
+ "cream",
+ "dairy",
+ "dessert",
+ "cold",
+ "soft",
+ "serve",
+ "cone",
+ "waffle"
+ ],
"moji": "🍨"
},
+ "ice_skate": {
+ "unicode": "26F8",
+ "unicode_alternates": "",
+ "name": "ice skate",
+ "shortname": ":ice_skate:",
+ "category": "activity",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": [
+ "place",
+ "sport",
+ "travel"
+ ]
+ },
"icecream": {
"unicode": "1F366",
"unicode_alternates": [],
@@ -7366,9 +14916,39 @@
"category": "objects",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["desert", "food", "hot", "icecream", "ice", "cream", "dairy", "dessert", "cold", "soft", "serve", "cone", "yogurt"],
+ "keywords": [
+ "desert",
+ "food",
+ "hot",
+ "icecream",
+ "ice",
+ "cream",
+ "dairy",
+ "dessert",
+ "cold",
+ "soft",
+ "serve",
+ "cone",
+ "yogurt"
+ ],
"moji": "🍦"
},
+ "id": {
+ "unicode": "1F194",
+ "unicode_alternates": "",
+ "name": "squared id",
+ "shortname": ":id:",
+ "category": "symbols",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": [
+ "purple-square",
+ "identification",
+ "identity",
+ "symbol",
+ "word"
+ ]
+ },
"ideograph_advantage": {
"unicode": "1F250",
"unicode_alternates": [],
@@ -7377,7 +14957,12 @@
"category": "other",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["chinese", "get", "kanji", "obtain"],
+ "keywords": [
+ "chinese",
+ "get",
+ "kanji",
+ "obtain"
+ ],
"moji": "🉐"
},
"imp": {
@@ -7388,7 +14973,14 @@
"category": "emoticons",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["angry", "devil", "evil", "horns", "cute", "devil"],
+ "keywords": [
+ "angry",
+ "devil",
+ "evil",
+ "horns",
+ "cute",
+ "devil"
+ ],
"moji": "👿"
},
"inbox_tray": {
@@ -7399,7 +14991,10 @@
"category": "objects",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["documents", "email"],
+ "keywords": [
+ "documents",
+ "email"
+ ],
"moji": "📥"
},
"incoming_envelope": {
@@ -7410,7 +15005,10 @@
"category": "objects",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["email", "inbox"],
+ "keywords": [
+ "email",
+ "inbox"
+ ],
"moji": "📨"
},
"info": {
@@ -7419,9 +15017,13 @@
"name": "circled information source",
"shortname": ":info:",
"category": "objects_symbols",
- "aliases": [":circled_information_source:"],
+ "aliases": [
+ ":circled_information_source:"
+ ],
"aliases_ascii": [],
- "keywords": ["icon"]
+ "keywords": [
+ "icon"
+ ]
},
"information_desk_person": {
"unicode": "1F481",
@@ -7431,18 +15033,147 @@
"category": "emoticons",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["female", "girl", "human", "woman", "information", "help", "question", "answer", "sassy", "unimpressed", "attitude", "snarky"],
+ "keywords": [
+ "female",
+ "girl",
+ "human",
+ "woman",
+ "information",
+ "help",
+ "question",
+ "answer",
+ "sassy",
+ "unimpressed",
+ "attitude",
+ "snarky"
+ ],
"moji": "💁"
},
+ "information_desk_person_tone1": {
+ "unicode": "1F481-1F3FB",
+ "unicode_alternates": "",
+ "name": "information desk person tone 1",
+ "shortname": ":information_desk_person_tone1:",
+ "category": "people",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": [
+ "female",
+ "girl",
+ "human",
+ "woman",
+ "help",
+ "question",
+ "answer",
+ "sassy",
+ "unimpressed",
+ "attitude",
+ "snarky"
+ ]
+ },
+ "information_desk_person_tone2": {
+ "unicode": "1F481-1F3FC",
+ "unicode_alternates": "",
+ "name": "information desk person tone 2",
+ "shortname": ":information_desk_person_tone2:",
+ "category": "people",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": [
+ "female",
+ "girl",
+ "human",
+ "woman",
+ "help",
+ "question",
+ "answer",
+ "sassy",
+ "unimpressed",
+ "attitude",
+ "snarky"
+ ]
+ },
+ "information_desk_person_tone3": {
+ "unicode": "1F481-1F3FD",
+ "unicode_alternates": "",
+ "name": "information desk person tone 3",
+ "shortname": ":information_desk_person_tone3:",
+ "category": "people",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": [
+ "female",
+ "girl",
+ "human",
+ "woman",
+ "help",
+ "question",
+ "answer",
+ "sassy",
+ "unimpressed",
+ "attitude",
+ "snarky"
+ ]
+ },
+ "information_desk_person_tone4": {
+ "unicode": "1F481-1F3FE",
+ "unicode_alternates": "",
+ "name": "information desk person tone 4",
+ "shortname": ":information_desk_person_tone4:",
+ "category": "people",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": [
+ "female",
+ "girl",
+ "human",
+ "woman",
+ "help",
+ "question",
+ "answer",
+ "sassy",
+ "unimpressed",
+ "attitude",
+ "snarky"
+ ]
+ },
+ "information_desk_person_tone5": {
+ "unicode": "1F481-1F3FF",
+ "unicode_alternates": "",
+ "name": "information desk person tone 5",
+ "shortname": ":information_desk_person_tone5:",
+ "category": "people",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": [
+ "female",
+ "girl",
+ "human",
+ "woman",
+ "help",
+ "question",
+ "answer",
+ "sassy",
+ "unimpressed",
+ "attitude",
+ "snarky"
+ ]
+ },
"information_source": {
"unicode": "2139",
- "unicode_alternates": ["2139-FE0F"],
+ "unicode_alternates": [
+ "2139-FE0F"
+ ],
"name": "information source",
"shortname": ":information_source:",
"category": "other",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["alphabet", "blue-square", "letter"],
+ "keywords": [
+ "alphabet",
+ "blue-square",
+ "letter"
+ ],
"moji": "ℹ"
},
"innocent": {
@@ -7452,19 +15183,49 @@
"shortname": ":innocent:",
"category": "emoticons",
"aliases": [],
- "aliases_ascii": ["O:-)", "0:-3", "0:3", "0:-)", "0:)", "0;^)", "O:-)", "O:)", "O;-)", "O=)", "0;-)", "O:-3", "O:3"],
- "keywords": ["angel", "face", "halo", "halo", "angel", "innocent", "ring", "circle", "heaven"],
+ "aliases_ascii": [
+ "O:-)",
+ "0:-3",
+ "0:3",
+ "0:-)",
+ "0:)",
+ "0;^)",
+ "O:-)",
+ "O:)",
+ "O;-)",
+ "O=)",
+ "0;-)",
+ "O:-3",
+ "O:3"
+ ],
+ "keywords": [
+ "angel",
+ "face",
+ "halo",
+ "halo",
+ "angel",
+ "innocent",
+ "ring",
+ "circle",
+ "heaven"
+ ],
"moji": "😇"
},
"interrobang": {
"unicode": "2049",
- "unicode_alternates": ["2049-FE0F"],
+ "unicode_alternates": [
+ "2049-FE0F"
+ ],
"name": "exclamation question mark",
"shortname": ":interrobang:",
"category": "other",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["punctuation", "surprise", "wat"],
+ "keywords": [
+ "punctuation",
+ "surprise",
+ "wat"
+ ],
"moji": "⁉"
},
"iphone": {
@@ -7475,7 +15236,12 @@
"category": "objects",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["apple", "dial", "gadgets", "technology"],
+ "keywords": [
+ "apple",
+ "dial",
+ "gadgets",
+ "technology"
+ ],
"moji": "📱"
},
"island": {
@@ -7484,9 +15250,15 @@
"name": "desert island",
"shortname": ":island:",
"category": "travel_places",
- "aliases": [":desert_island:"],
+ "aliases": [
+ ":desert_island:"
+ ],
"aliases_ascii": [],
- "keywords": ["land", "solitude", "alone"]
+ "keywords": [
+ "land",
+ "solitude",
+ "alone"
+ ]
},
"izakaya_lantern": {
"unicode": "1F3EE",
@@ -7496,7 +15268,17 @@
"category": "places",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["light", "izakaya", "lantern", "stay", "drink", "alcohol", "bar", "sake", "restaurant"],
+ "keywords": [
+ "light",
+ "izakaya",
+ "lantern",
+ "stay",
+ "drink",
+ "alcohol",
+ "bar",
+ "sake",
+ "restaurant"
+ ],
"moji": "🏮"
},
"jack_o_lantern": {
@@ -7507,7 +15289,24 @@
"category": "objects",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["halloween", "jack-o-lantern", "pumpkin", "halloween", "holiday", "carve", "autumn", "fall", "october", "saints", "costume", "spooky", "horror", "scary", "scared", "dead"],
+ "keywords": [
+ "halloween",
+ "jack-o-lantern",
+ "pumpkin",
+ "halloween",
+ "holiday",
+ "carve",
+ "autumn",
+ "fall",
+ "october",
+ "saints",
+ "costume",
+ "spooky",
+ "horror",
+ "scary",
+ "scared",
+ "dead"
+ ],
"moji": "🎃"
},
"japan": {
@@ -7518,7 +15317,9 @@
"category": "places",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["nation"],
+ "keywords": [
+ "nation"
+ ],
"moji": "🗾"
},
"japanese_castle": {
@@ -7529,7 +15330,17 @@
"category": "places",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["building", "photo", "castle", "japanese", "residence", "royalty", "fort", "fortified", "fortress"],
+ "keywords": [
+ "building",
+ "photo",
+ "castle",
+ "japanese",
+ "residence",
+ "royalty",
+ "fort",
+ "fortified",
+ "fortress"
+ ],
"moji": "🏯"
},
"japanese_goblin": {
@@ -7540,7 +15351,24 @@
"category": "emoticons",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["evil", "mask", "red", "japanese", "tengu", "supernatural", "avian", "demon", "goblin", "mask", "theater", "nose", "frown", "mustache", "anger", "frustration"],
+ "keywords": [
+ "evil",
+ "mask",
+ "red",
+ "japanese",
+ "tengu",
+ "supernatural",
+ "avian",
+ "demon",
+ "goblin",
+ "mask",
+ "theater",
+ "nose",
+ "frown",
+ "mustache",
+ "anger",
+ "frustration"
+ ],
"moji": "👺"
},
"japanese_ogre": {
@@ -7551,7 +15379,21 @@
"category": "emoticons",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["monster", "japanese", "oni", "demon", "troll", "ogre", "folklore", "monster", "devil", "mask", "theater", "horns", "teeth"],
+ "keywords": [
+ "monster",
+ "japanese",
+ "oni",
+ "demon",
+ "troll",
+ "ogre",
+ "folklore",
+ "monster",
+ "devil",
+ "mask",
+ "theater",
+ "horns",
+ "teeth"
+ ],
"moji": "👹"
},
"jeans": {
@@ -7562,7 +15404,19 @@
"category": "emoticons",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["fashion", "shopping", "jeans", "pants", "blue", "denim", "levi&#039;s", "levi", "designer", "work", "skinny"],
+ "keywords": [
+ "fashion",
+ "shopping",
+ "jeans",
+ "pants",
+ "blue",
+ "denim",
+ "levi&#039;s",
+ "levi",
+ "designer",
+ "work",
+ "skinny"
+ ],
"moji": "👖"
},
"jet_up": {
@@ -7571,9 +15425,13 @@
"name": "up-pointing military airplane",
"shortname": ":jet_up:",
"category": "travel_places",
- "aliases": [":up_pointing_military_airplane:"],
+ "aliases": [
+ ":up_pointing_military_airplane:"
+ ],
"aliases_ascii": [],
- "keywords": ["jet"]
+ "keywords": [
+ "jet"
+ ]
},
"joy": {
"unicode": "1F602",
@@ -7582,8 +15440,22 @@
"shortname": ":joy:",
"category": "emoticons",
"aliases": [],
- "aliases_ascii": [":')", ":'-)"],
- "keywords": ["cry", "face", "haha", "happy", "tears", "tears", "cry", "joy", "happy", "weep"],
+ "aliases_ascii": [
+ ":')",
+ ":'-)"
+ ],
+ "keywords": [
+ "cry",
+ "face",
+ "haha",
+ "happy",
+ "tears",
+ "tears",
+ "cry",
+ "joy",
+ "happy",
+ "weep"
+ ],
"moji": "😂"
},
"joy_cat": {
@@ -7594,7 +15466,17 @@
"category": "emoticons",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["animal", "cats", "haha", "happy", "tears", "happy", "tears", "cry", "joy"],
+ "keywords": [
+ "animal",
+ "cats",
+ "haha",
+ "happy",
+ "tears",
+ "happy",
+ "tears",
+ "cry",
+ "joy"
+ ],
"moji": "😹"
},
"joystick": {
@@ -7605,7 +15487,21 @@
"category": "objects_symbols",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["games", "atari", "controller"]
+ "keywords": [
+ "games",
+ "atari",
+ "controller"
+ ]
+ },
+ "kaaba": {
+ "unicode": "1F54B",
+ "unicode_alternates": "",
+ "name": "kaaba",
+ "shortname": ":kaaba:",
+ "category": "travel",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": []
},
"key": {
"unicode": "1F511",
@@ -7615,7 +15511,11 @@
"category": "objects",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["door", "lock", "password"],
+ "keywords": [
+ "door",
+ "lock",
+ "password"
+ ],
"moji": "🔑"
},
"key2": {
@@ -7624,9 +15524,16 @@
"name": "old key",
"shortname": ":key2:",
"category": "objects_symbols",
- "aliases": [":old_key:"],
+ "aliases": [
+ ":old_key:"
+ ],
"aliases_ascii": [],
- "keywords": ["door", "lock", "password", "skeleton"]
+ "keywords": [
+ "door",
+ "lock",
+ "password",
+ "skeleton"
+ ]
},
"keyboard": {
"unicode": "1F5AE",
@@ -7634,9 +15541,16 @@
"name": "wired keyboard",
"shortname": ":keyboard:",
"category": "objects_symbols",
- "aliases": [":wired_keyboard:"],
+ "aliases": [
+ ":wired_keyboard:"
+ ],
"aliases_ascii": [],
- "keywords": ["typing", "keys", "input", "device"]
+ "keywords": [
+ "typing",
+ "keys",
+ "input",
+ "device"
+ ]
},
"keyboard_mouse": {
"unicode": "1F5A6",
@@ -7644,9 +15558,15 @@
"name": "keyboard and mouse",
"shortname": ":keyboard_mouse:",
"category": "objects_symbols",
- "aliases": [":keyboard_and_mouse:"],
+ "aliases": [
+ ":keyboard_and_mouse:"
+ ],
"aliases_ascii": [],
- "keywords": ["computer", "input", "desktop"]
+ "keywords": [
+ "computer",
+ "input",
+ "desktop"
+ ]
},
"keyboard_with_jacks": {
"unicode": "1F398",
@@ -7654,9 +15574,15 @@
"name": "musical keyboard with jacks",
"shortname": ":keyboard_with_jacks:",
"category": "objects_symbols",
- "aliases": [":musical_keyboard_with_jacks:"],
+ "aliases": [
+ ":musical_keyboard_with_jacks:"
+ ],
"aliases_ascii": [],
- "keywords": ["music", "instrument", "midi"]
+ "keywords": [
+ "music",
+ "instrument",
+ "midi"
+ ]
},
"keycap_ten": {
"unicode": "1F51F",
@@ -7666,7 +15592,11 @@
"category": "other",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["10", "blue-square", "numbers"],
+ "keywords": [
+ "10",
+ "blue-square",
+ "numbers"
+ ],
"moji": "🔟"
},
"kimono": {
@@ -7677,7 +15607,13 @@
"category": "emoticons",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["dress", "fashion", "female", "japanese", "women"],
+ "keywords": [
+ "dress",
+ "fashion",
+ "female",
+ "japanese",
+ "women"
+ ],
"moji": "👘"
},
"kiss": {
@@ -7688,28 +15624,57 @@
"category": "emoticons",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["affection", "face", "like", "lips", "love", "valentines"],
+ "keywords": [
+ "affection",
+ "face",
+ "like",
+ "lips",
+ "love",
+ "valentines"
+ ],
"moji": "💋"
},
"kiss_mm": {
"unicode": "1F468-2764-1F48B-1F468",
- "unicode_alternates": ["1F468-200D-2764-FE0F-200D-1F48B-200D-1F468"],
+ "unicode_alternates": [
+ "1F468-200D-2764-FE0F-200D-1F48B-200D-1F468"
+ ],
"name": "kiss (man,man)",
"shortname": ":kiss_mm:",
"category": "people",
- "aliases": [":couplekiss_mm:"],
- "aliases_ascii": [],
- "keywords": ["dating", "like", "love", "marriage", "valentines", "couple"]
+ "aliases": [
+ ":couplekiss_mm:"
+ ],
+ "aliases_ascii": [],
+ "keywords": [
+ "dating",
+ "like",
+ "love",
+ "marriage",
+ "valentines",
+ "couple"
+ ]
},
"kiss_ww": {
"unicode": "1F469-2764-1F48B-1F469",
- "unicode_alternates": ["1F469-200D-2764-FE0F-200D-1F48B-200D-1F469"],
+ "unicode_alternates": [
+ "1F469-200D-2764-FE0F-200D-1F48B-200D-1F469"
+ ],
"name": "kiss (woman,woman)",
"shortname": ":kiss_ww:",
"category": "people",
- "aliases": [":couplekiss_ww:"],
- "aliases_ascii": [],
- "keywords": ["dating", "like", "love", "marriage", "valentines", "couple"]
+ "aliases": [
+ ":couplekiss_ww:"
+ ],
+ "aliases_ascii": [],
+ "keywords": [
+ "dating",
+ "like",
+ "love",
+ "marriage",
+ "valentines",
+ "couple"
+ ]
},
"kissing": {
"unicode": "1F617",
@@ -7719,7 +15684,19 @@
"category": "emoticons",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["3", "face", "infatuation", "like", "love", "valentines", "kissing", "kiss", "pucker", "lips", "smooch"],
+ "keywords": [
+ "3",
+ "face",
+ "infatuation",
+ "like",
+ "love",
+ "valentines",
+ "kissing",
+ "kiss",
+ "pucker",
+ "lips",
+ "smooch"
+ ],
"moji": "😗"
},
"kissing_cat": {
@@ -7730,7 +15707,15 @@
"category": "emoticons",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["animal", "cats", "passion", "kiss", "puckered", "heart", "love"],
+ "keywords": [
+ "animal",
+ "cats",
+ "passion",
+ "kiss",
+ "puckered",
+ "heart",
+ "love"
+ ],
"moji": "😽"
},
"kissing_closed_eyes": {
@@ -7741,7 +15726,21 @@
"category": "emoticons",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["affection", "face", "infatuation", "like", "love", "valentines", "kissing", "kiss", "passion", "puckered", "heart", "love", "smooch"],
+ "keywords": [
+ "affection",
+ "face",
+ "infatuation",
+ "like",
+ "love",
+ "valentines",
+ "kissing",
+ "kiss",
+ "passion",
+ "puckered",
+ "heart",
+ "love",
+ "smooch"
+ ],
"moji": "😚"
},
"kissing_heart": {
@@ -7751,8 +15750,25 @@
"shortname": ":kissing_heart:",
"category": "emoticons",
"aliases": [],
- "aliases_ascii": [":*", ":-*", "=*", ":^*"],
- "keywords": ["affection", "face", "infatuation", "kiss", "blowing kiss", "heart", "love", "lips", "like", "love", "valentines"],
+ "aliases_ascii": [
+ ":*",
+ ":-*",
+ "=*",
+ ":^*"
+ ],
+ "keywords": [
+ "affection",
+ "face",
+ "infatuation",
+ "kiss",
+ "blowing kiss",
+ "heart",
+ "love",
+ "lips",
+ "like",
+ "love",
+ "valentines"
+ ],
"moji": "😘"
},
"kissing_smiling_eyes": {
@@ -7763,7 +15779,18 @@
"category": "emoticons",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["affection", "face", "infatuation", "valentines", "kissing", "kiss", "smile", "pucker", "lips", "smooch"],
+ "keywords": [
+ "affection",
+ "face",
+ "infatuation",
+ "valentines",
+ "kissing",
+ "kiss",
+ "smile",
+ "pucker",
+ "lips",
+ "smooch"
+ ],
"moji": "😙"
},
"knife": {
@@ -7785,7 +15812,10 @@
"category": "nature",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["animal", "nature"],
+ "keywords": [
+ "animal",
+ "nature"
+ ],
"moji": "🐨"
},
"koko": {
@@ -7796,7 +15826,13 @@
"category": "other",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["blue-square", "destination", "here", "japanese", "katakana"],
+ "keywords": [
+ "blue-square",
+ "destination",
+ "here",
+ "japanese",
+ "katakana"
+ ],
"moji": "🈁"
},
"label": {
@@ -7807,7 +15843,9 @@
"category": "objects_symbols",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["tag"]
+ "keywords": [
+ "tag"
+ ]
},
"large_blue_circle": {
"unicode": "1F535",
@@ -7828,7 +15866,9 @@
"category": "other",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["shape"],
+ "keywords": [
+ "shape"
+ ],
"moji": "🔷"
},
"large_orange_diamond": {
@@ -7839,7 +15879,9 @@
"category": "other",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["shape"],
+ "keywords": [
+ "shape"
+ ],
"moji": "🔶"
},
"last_quarter_moon": {
@@ -7850,7 +15892,16 @@
"category": "nature",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["nature", "moon", "last", "quarter", "sky", "night", "cheese", "phase"],
+ "keywords": [
+ "nature",
+ "moon",
+ "last",
+ "quarter",
+ "sky",
+ "night",
+ "cheese",
+ "phase"
+ ],
"moji": "🌗"
},
"last_quarter_moon_with_face": {
@@ -7861,7 +15912,18 @@
"category": "nature",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["nature", "moon", "last", "quarter", "anthropomorphic", "face", "sky", "night", "cheese", "phase"],
+ "keywords": [
+ "nature",
+ "moon",
+ "last",
+ "quarter",
+ "anthropomorphic",
+ "face",
+ "sky",
+ "night",
+ "cheese",
+ "phase"
+ ],
"moji": "🌜"
},
"laughing": {
@@ -7870,9 +15932,23 @@
"name": "smiling face with open mouth and tightly-closed ey",
"shortname": ":laughing:",
"category": "emoticons",
- "aliases": [":satisfied:"],
- "aliases_ascii": [">:)", ">;)", ">:-)", ">=)"],
- "keywords": ["happy", "joy", "lol", "smiling", "laughing", "laugh"],
+ "aliases": [
+ ":satisfied:"
+ ],
+ "aliases_ascii": [
+ ">:)",
+ ">;)",
+ ">:-)",
+ ">=)"
+ ],
+ "keywords": [
+ "happy",
+ "joy",
+ "lol",
+ "smiling",
+ "laughing",
+ "laugh"
+ ],
"moji": "😆"
},
"leaves": {
@@ -7883,7 +15959,19 @@
"category": "nature",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["grass", "lawn", "nature", "plant", "tree", "vegetable", "leaves", "leaf", "wind", "float", "fluttering"],
+ "keywords": [
+ "grass",
+ "lawn",
+ "nature",
+ "plant",
+ "tree",
+ "vegetable",
+ "leaves",
+ "leaf",
+ "wind",
+ "float",
+ "fluttering"
+ ],
"moji": "🍃"
},
"ledger": {
@@ -7894,7 +15982,10 @@
"category": "objects",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["notes", "paper"],
+ "keywords": [
+ "notes",
+ "paper"
+ ],
"moji": "📒"
},
"left_luggage": {
@@ -7905,7 +15996,14 @@
"category": "other",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["blue-square", "travel", "bag", "baggage", "luggage", "travel"],
+ "keywords": [
+ "blue-square",
+ "travel",
+ "bag",
+ "baggage",
+ "luggage",
+ "travel"
+ ],
"moji": "🛅"
},
"left_receiver": {
@@ -7914,24 +16012,36 @@
"name": "left hand telephone receiver",
"shortname": ":left_receiver:",
"category": "objects_symbols",
- "aliases": [":left_hand_telephone_receiver:"],
+ "aliases": [
+ ":left_hand_telephone_receiver:"
+ ],
"aliases_ascii": [],
- "keywords": ["communication", "dial", "technology"]
+ "keywords": [
+ "communication",
+ "dial",
+ "technology"
+ ]
},
"left_right_arrow": {
"unicode": "2194",
- "unicode_alternates": ["2194-FE0F"],
+ "unicode_alternates": [
+ "2194-FE0F"
+ ],
"name": "left right arrow",
"shortname": ":left_right_arrow:",
"category": "other",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["shape"],
+ "keywords": [
+ "shape"
+ ],
"moji": "↔"
},
"leftwards_arrow_with_hook": {
"unicode": "21A9",
- "unicode_alternates": ["21A9-FE0F"],
+ "unicode_alternates": [
+ "21A9-FE0F"
+ ],
"name": "leftwards arrow with hook",
"shortname": ":leftwards_arrow_with_hook:",
"category": "other",
@@ -7948,18 +16058,39 @@
"category": "objects",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["fruit", "nature", "lemon", "yellow", "citrus"],
+ "keywords": [
+ "fruit",
+ "nature",
+ "lemon",
+ "yellow",
+ "citrus"
+ ],
"moji": "🍋"
},
"leo": {
"unicode": "264C",
- "unicode_alternates": ["264C-FE0F"],
+ "unicode_alternates": [
+ "264C-FE0F"
+ ],
"name": "leo",
"shortname": ":leo:",
"category": "other",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["leo", "lion", "astrology", "greek", "constellation", "stars", "zodiac", "sign", "purple-square", "sign", "zodiac", "horoscope"],
+ "keywords": [
+ "leo",
+ "lion",
+ "astrology",
+ "greek",
+ "constellation",
+ "stars",
+ "zodiac",
+ "sign",
+ "purple-square",
+ "sign",
+ "zodiac",
+ "horoscope"
+ ],
"moji": "♌"
},
"leopard": {
@@ -7970,7 +16101,15 @@
"category": "nature",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["animal", "nature", "leopard", "cat", "spot", "spotted", "sexy"],
+ "keywords": [
+ "animal",
+ "nature",
+ "leopard",
+ "cat",
+ "spot",
+ "spotted",
+ "sexy"
+ ],
"moji": "🐆"
},
"level_slider": {
@@ -7981,7 +16120,9 @@
"category": "objects_symbols",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["controls"]
+ "keywords": [
+ "controls"
+ ]
},
"levitate": {
"unicode": "1F574",
@@ -7989,19 +16130,39 @@
"name": "man in business suit levitating",
"shortname": ":levitate:",
"category": "people",
- "aliases": [":man_in_business_suit_levitating:"],
+ "aliases": [
+ ":man_in_business_suit_levitating:"
+ ],
"aliases_ascii": [],
- "keywords": ["hover", "exclamation"]
+ "keywords": [
+ "hover",
+ "exclamation"
+ ]
},
"libra": {
"unicode": "264E",
- "unicode_alternates": ["264E-FE0F"],
+ "unicode_alternates": [
+ "264E-FE0F"
+ ],
"name": "libra",
"shortname": ":libra:",
"category": "other",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["libra", "scales", "astrology", "greek", "constellation", "stars", "zodiac", "sign", "purple-square", "sign", "zodiac", "horoscope"],
+ "keywords": [
+ "libra",
+ "scales",
+ "astrology",
+ "greek",
+ "constellation",
+ "stars",
+ "zodiac",
+ "sign",
+ "purple-square",
+ "sign",
+ "zodiac",
+ "horoscope"
+ ],
"moji": "♎"
},
"lifter": {
@@ -8010,9 +16171,101 @@
"name": "weight lifter",
"shortname": ":lifter:",
"category": "activity",
- "aliases": [":weight_lifter:"],
+ "aliases": [
+ ":weight_lifter:"
+ ],
+ "aliases_ascii": [],
+ "keywords": [
+ "bench",
+ "press",
+ "squats",
+ "deadlift"
+ ]
+ },
+ "lifter_tone1": {
+ "unicode": "1F3CB-1F3FB",
+ "unicode_alternates": "",
+ "name": "weight lifter tone 1",
+ "shortname": ":lifter_tone1:",
+ "category": "activity",
+ "aliases": [
+ ":weight_lifter_tone1:"
+ ],
+ "aliases_ascii": [],
+ "keywords": [
+ "bench",
+ "press",
+ "squats",
+ "deadlift"
+ ]
+ },
+ "lifter_tone2": {
+ "unicode": "1F3CB-1F3FC",
+ "unicode_alternates": "",
+ "name": "weight lifter tone 2",
+ "shortname": ":lifter_tone2:",
+ "category": "activity",
+ "aliases": [
+ ":weight_lifter_tone2:"
+ ],
+ "aliases_ascii": [],
+ "keywords": [
+ "bench",
+ "press",
+ "squats",
+ "deadlift"
+ ]
+ },
+ "lifter_tone3": {
+ "unicode": "1F3CB-1F3FD",
+ "unicode_alternates": "",
+ "name": "weight lifter tone 3",
+ "shortname": ":lifter_tone3:",
+ "category": "activity",
+ "aliases": [
+ ":weight_lifter_tone3:"
+ ],
+ "aliases_ascii": [],
+ "keywords": [
+ "bench",
+ "press",
+ "squats",
+ "deadlift"
+ ]
+ },
+ "lifter_tone4": {
+ "unicode": "1F3CB-1F3FE",
+ "unicode_alternates": "",
+ "name": "weight lifter tone 4",
+ "shortname": ":lifter_tone4:",
+ "category": "activity",
+ "aliases": [
+ ":weight_lifter_tone4:"
+ ],
+ "aliases_ascii": [],
+ "keywords": [
+ "bench",
+ "press",
+ "squats",
+ "deadlift"
+ ]
+ },
+ "lifter_tone5": {
+ "unicode": "1F3CB-1F3FF",
+ "unicode_alternates": "",
+ "name": "weight lifter tone 5",
+ "shortname": ":lifter_tone5:",
+ "category": "activity",
+ "aliases": [
+ ":weight_lifter_tone5:"
+ ],
"aliases_ascii": [],
- "keywords": ["bench", "press", "squats", "deadlift"]
+ "keywords": [
+ "bench",
+ "press",
+ "squats",
+ "deadlift"
+ ]
},
"light_check_mark": {
"unicode": "1F5F8",
@@ -8020,9 +16273,13 @@
"name": "light check mark",
"shortname": ":light_check_mark:",
"category": "objects_symbols",
- "aliases": [":light_mark:"],
+ "aliases": [
+ ":light_mark:"
+ ],
"aliases_ascii": [],
- "keywords": ["vote"]
+ "keywords": [
+ "vote"
+ ]
},
"light_rail": {
"unicode": "1F688",
@@ -8032,7 +16289,13 @@
"category": "places",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["transportation", "vehicle", "train", "rail", "light"],
+ "keywords": [
+ "transportation",
+ "vehicle",
+ "train",
+ "rail",
+ "light"
+ ],
"moji": "🚈"
},
"link": {
@@ -8043,9 +16306,24 @@
"category": "other",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["rings", "url"],
+ "keywords": [
+ "rings",
+ "url"
+ ],
"moji": "🔗"
},
+ "lion_face": {
+ "unicode": "1F981",
+ "unicode_alternates": "",
+ "name": "lion face",
+ "shortname": ":lion_face:",
+ "category": "nature",
+ "aliases": [
+ ":lion:"
+ ],
+ "aliases_ascii": [],
+ "keywords": []
+ },
"lips": {
"unicode": "1F444",
"unicode_alternates": [],
@@ -8054,7 +16332,10 @@
"category": "emoticons",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["kiss", "mouth"],
+ "keywords": [
+ "kiss",
+ "mouth"
+ ],
"moji": "👄"
},
"lips2": {
@@ -8065,7 +16346,10 @@
"category": "people",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["kiss", "mouth"]
+ "keywords": [
+ "kiss",
+ "mouth"
+ ]
},
"lipstick": {
"unicode": "1F484",
@@ -8075,7 +16359,11 @@
"category": "emoticons",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["fashion", "female", "girl"],
+ "keywords": [
+ "fashion",
+ "female",
+ "girl"
+ ],
"moji": "💄"
},
"lock": {
@@ -8086,7 +16374,10 @@
"category": "objects",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["password", "security"],
+ "keywords": [
+ "password",
+ "security"
+ ],
"moji": "🔒"
},
"lock_with_ink_pen": {
@@ -8097,7 +16388,10 @@
"category": "objects",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["secret", "security"],
+ "keywords": [
+ "secret",
+ "security"
+ ],
"moji": "🔏"
},
"lollipop": {
@@ -8108,7 +16402,18 @@
"category": "objects",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["candy", "food", "snack", "sweet", "lollipop", "stick", "lick", "sweet", "sugar", "candy"],
+ "keywords": [
+ "candy",
+ "food",
+ "snack",
+ "sweet",
+ "lollipop",
+ "stick",
+ "lick",
+ "sweet",
+ "sugar",
+ "candy"
+ ],
"moji": "🍭"
},
"loop": {
@@ -8119,7 +16424,9 @@
"category": "other",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["curly"],
+ "keywords": [
+ "curly"
+ ],
"moji": "➿"
},
"loud_sound": {
@@ -8141,7 +16448,10 @@
"category": "objects",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["sound", "volume"],
+ "keywords": [
+ "sound",
+ "volume"
+ ],
"moji": "📢"
},
"love_hotel": {
@@ -8152,7 +16462,22 @@
"category": "places",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["affection", "dating", "like", "love", "hotel", "love", "sex", "romance", "leisure", "adultery", "prostitution", "hospital", "birth", "happy"],
+ "keywords": [
+ "affection",
+ "dating",
+ "like",
+ "love",
+ "hotel",
+ "love",
+ "sex",
+ "romance",
+ "leisure",
+ "adultery",
+ "prostitution",
+ "hospital",
+ "birth",
+ "happy"
+ ],
"moji": "🏩"
},
"love_letter": {
@@ -8163,7 +16488,17 @@
"category": "emoticons",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["affection", "email", "envelope", "like", "valentines", "love", "letter", "kiss", "heart"],
+ "keywords": [
+ "affection",
+ "email",
+ "envelope",
+ "like",
+ "valentines",
+ "love",
+ "letter",
+ "kiss",
+ "heart"
+ ],
"moji": "💌"
},
"low_brightness": {
@@ -8174,18 +16509,27 @@
"category": "objects",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["summer", "sun"],
+ "keywords": [
+ "summer",
+ "sun"
+ ],
"moji": "🔅"
},
"m": {
"unicode": "24C2",
- "unicode_alternates": ["24C2-FE0F"],
+ "unicode_alternates": [
+ "24C2-FE0F"
+ ],
"name": "circled latin capital letter m",
"shortname": ":m:",
"category": "other",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["alphabet", "blue-circle", "letter"],
+ "keywords": [
+ "alphabet",
+ "blue-circle",
+ "letter"
+ ],
"moji": "Ⓜ"
},
"mag": {
@@ -8196,7 +16540,14 @@
"category": "objects",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["search", "zoom", "detective", "investigator", "detail", "details"],
+ "keywords": [
+ "search",
+ "zoom",
+ "detective",
+ "investigator",
+ "detail",
+ "details"
+ ],
"moji": "🔍"
},
"mag_right": {
@@ -8207,18 +16558,31 @@
"category": "objects",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["search", "zoom", "detective", "investigator", "detail", "details"],
+ "keywords": [
+ "search",
+ "zoom",
+ "detective",
+ "investigator",
+ "detail",
+ "details"
+ ],
"moji": "🔎"
},
"mahjong": {
"unicode": "1F004",
- "unicode_alternates": ["1F004-FE0F"],
+ "unicode_alternates": [
+ "1F004-FE0F"
+ ],
"name": "mahjong tile red dragon",
"shortname": ":mahjong:",
"category": "objects",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["chinese", "game", "kanji"],
+ "keywords": [
+ "chinese",
+ "game",
+ "kanji"
+ ],
"moji": "🀄"
},
"mailbox": {
@@ -8229,7 +16593,11 @@
"category": "objects",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["communication", "email", "inbox"],
+ "keywords": [
+ "communication",
+ "email",
+ "inbox"
+ ],
"moji": "📫"
},
"mailbox_closed": {
@@ -8240,7 +16608,11 @@
"category": "objects",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["communication", "email", "inbox"],
+ "keywords": [
+ "communication",
+ "email",
+ "inbox"
+ ],
"moji": "📪"
},
"mailbox_with_mail": {
@@ -8251,7 +16623,11 @@
"category": "objects",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["communication", "email", "inbox"],
+ "keywords": [
+ "communication",
+ "email",
+ "inbox"
+ ],
"moji": "📬"
},
"mailbox_with_no_mail": {
@@ -8262,7 +16638,10 @@
"category": "objects",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["email", "inbox"],
+ "keywords": [
+ "email",
+ "inbox"
+ ],
"moji": "📭"
},
"man": {
@@ -8273,9 +16652,95 @@
"category": "emoticons",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["classy", "dad", "father", "guy", "mustashe"],
+ "keywords": [
+ "classy",
+ "dad",
+ "father",
+ "guy",
+ "mustashe"
+ ],
"moji": "👨"
},
+ "man_tone1": {
+ "unicode": "1F468-1F3FB",
+ "unicode_alternates": "",
+ "name": "man tone 1",
+ "shortname": ":man_tone1:",
+ "category": "people",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": [
+ "classy",
+ "dad",
+ "father",
+ "guy",
+ "mustache"
+ ]
+ },
+ "man_tone2": {
+ "unicode": "1F468-1F3FC",
+ "unicode_alternates": "",
+ "name": "man tone 2",
+ "shortname": ":man_tone2:",
+ "category": "people",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": [
+ "classy",
+ "dad",
+ "father",
+ "guy",
+ "mustache"
+ ]
+ },
+ "man_tone3": {
+ "unicode": "1F468-1F3FD",
+ "unicode_alternates": "",
+ "name": "man tone 3",
+ "shortname": ":man_tone3:",
+ "category": "people",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": [
+ "classy",
+ "dad",
+ "father",
+ "guy",
+ "mustache"
+ ]
+ },
+ "man_tone4": {
+ "unicode": "1F468-1F3FE",
+ "unicode_alternates": "",
+ "name": "man tone 4",
+ "shortname": ":man_tone4:",
+ "category": "people",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": [
+ "classy",
+ "dad",
+ "father",
+ "guy",
+ "mustache"
+ ]
+ },
+ "man_tone5": {
+ "unicode": "1F468-1F3FF",
+ "unicode_alternates": "",
+ "name": "man tone 5",
+ "shortname": ":man_tone5:",
+ "category": "people",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": [
+ "classy",
+ "dad",
+ "father",
+ "guy",
+ "mustache"
+ ]
+ },
"man_with_gua_pi_mao": {
"unicode": "1F472",
"unicode_alternates": [],
@@ -8284,9 +16749,101 @@
"category": "emoticons",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["boy", "male", "skullcap", "chinese", "asian", "qing"],
+ "keywords": [
+ "boy",
+ "male",
+ "skullcap",
+ "chinese",
+ "asian",
+ "qing"
+ ],
"moji": "👲"
},
+ "man_with_gua_pi_mao_tone1": {
+ "unicode": "1F472-1F3FB",
+ "unicode_alternates": "",
+ "name": "man with gua pi mao tone 1",
+ "shortname": ":man_with_gua_pi_mao_tone1:",
+ "category": "people",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": [
+ "boy",
+ "male",
+ "skullcap",
+ "chinese",
+ "asian",
+ "qing"
+ ]
+ },
+ "man_with_gua_pi_mao_tone2": {
+ "unicode": "1F472-1F3FC",
+ "unicode_alternates": "",
+ "name": "man with gua pi mao tone 2",
+ "shortname": ":man_with_gua_pi_mao_tone2:",
+ "category": "people",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": [
+ "boy",
+ "male",
+ "skullcap",
+ "chinese",
+ "asian",
+ "qing"
+ ]
+ },
+ "man_with_gua_pi_mao_tone3": {
+ "unicode": "1F472-1F3FD",
+ "unicode_alternates": "",
+ "name": "man with gua pi mao tone 3",
+ "shortname": ":man_with_gua_pi_mao_tone3:",
+ "category": "people",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": [
+ "boy",
+ "male",
+ "skullcap",
+ "chinese",
+ "asian",
+ "qing"
+ ]
+ },
+ "man_with_gua_pi_mao_tone4": {
+ "unicode": "1F472-1F3FE",
+ "unicode_alternates": "",
+ "name": "man with gua pi mao tone 4",
+ "shortname": ":man_with_gua_pi_mao_tone4:",
+ "category": "people",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": [
+ "boy",
+ "male",
+ "skullcap",
+ "chinese",
+ "asian",
+ "qing"
+ ]
+ },
+ "man_with_gua_pi_mao_tone5": {
+ "unicode": "1F472-1F3FF",
+ "unicode_alternates": "",
+ "name": "man with gua pi mao tone 5",
+ "shortname": ":man_with_gua_pi_mao_tone5:",
+ "category": "people",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": [
+ "boy",
+ "male",
+ "skullcap",
+ "chinese",
+ "asian",
+ "qing"
+ ]
+ },
"man_with_turban": {
"unicode": "1F473",
"unicode_alternates": [],
@@ -8295,9 +16852,120 @@
"category": "emoticons",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["male", "turban", "headdress", "headwear", "pagri", "india", "indian", "mummy", "wisdom", "peace"],
+ "keywords": [
+ "male",
+ "turban",
+ "headdress",
+ "headwear",
+ "pagri",
+ "india",
+ "indian",
+ "mummy",
+ "wisdom",
+ "peace"
+ ],
"moji": "👳"
},
+ "man_with_turban_tone1": {
+ "unicode": "1F473-1F3FB",
+ "unicode_alternates": "",
+ "name": "man with turban tone 1",
+ "shortname": ":man_with_turban_tone1:",
+ "category": "people",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": [
+ "male",
+ "headdress",
+ "headwear",
+ "pagri",
+ "india",
+ "indian",
+ "mummy",
+ "wisdom",
+ "peace"
+ ]
+ },
+ "man_with_turban_tone2": {
+ "unicode": "1F473-1F3FC",
+ "unicode_alternates": "",
+ "name": "man with turban tone 2",
+ "shortname": ":man_with_turban_tone2:",
+ "category": "people",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": [
+ "male",
+ "headdress",
+ "headwear",
+ "pagri",
+ "india",
+ "indian",
+ "mummy",
+ "wisdom",
+ "peace"
+ ]
+ },
+ "man_with_turban_tone3": {
+ "unicode": "1F473-1F3FD",
+ "unicode_alternates": "",
+ "name": "man with turban tone 3",
+ "shortname": ":man_with_turban_tone3:",
+ "category": "people",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": [
+ "male",
+ "headdress",
+ "headwear",
+ "pagri",
+ "india",
+ "indian",
+ "mummy",
+ "wisdom",
+ "peace"
+ ]
+ },
+ "man_with_turban_tone4": {
+ "unicode": "1F473-1F3FE",
+ "unicode_alternates": "",
+ "name": "man with turban tone 4",
+ "shortname": ":man_with_turban_tone4:",
+ "category": "people",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": [
+ "male",
+ "headdress",
+ "headwear",
+ "pagri",
+ "india",
+ "indian",
+ "mummy",
+ "wisdom",
+ "peace"
+ ]
+ },
+ "man_with_turban_tone5": {
+ "unicode": "1F473-1F3FF",
+ "unicode_alternates": "",
+ "name": "man with turban tone 5",
+ "shortname": ":man_with_turban_tone5:",
+ "category": "people",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": [
+ "male",
+ "headdress",
+ "headwear",
+ "pagri",
+ "india",
+ "indian",
+ "mummy",
+ "wisdom",
+ "peace"
+ ]
+ },
"mans_shoe": {
"unicode": "1F45E",
"unicode_alternates": [],
@@ -8306,7 +16974,10 @@
"category": "emoticons",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["fashion", "male"],
+ "keywords": [
+ "fashion",
+ "male"
+ ],
"moji": "👞"
},
"map": {
@@ -8315,9 +16986,15 @@
"name": "world map",
"shortname": ":map:",
"category": "travel_places",
- "aliases": [":world_map:"],
+ "aliases": [
+ ":world_map:"
+ ],
"aliases_ascii": [],
- "keywords": ["atlas", "earth", "cartography"]
+ "keywords": [
+ "atlas",
+ "earth",
+ "cartography"
+ ]
},
"maple_leaf": {
"unicode": "1F341",
@@ -8327,7 +17004,17 @@
"category": "nature",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["canada", "nature", "plant", "vegetable", "maple", "leaf", "syrup", "canada", "tree"],
+ "keywords": [
+ "canada",
+ "nature",
+ "plant",
+ "vegetable",
+ "maple",
+ "leaf",
+ "syrup",
+ "canada",
+ "tree"
+ ],
"moji": "🍁"
},
"mask": {
@@ -8338,7 +17025,16 @@
"category": "emoticons",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["face", "ill", "sick", "sick", "virus", "flu", "medical", "mask"],
+ "keywords": [
+ "face",
+ "ill",
+ "sick",
+ "sick",
+ "virus",
+ "flu",
+ "medical",
+ "mask"
+ ],
"moji": "😷"
},
"massage": {
@@ -8349,9 +17045,83 @@
"category": "emoticons",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["female", "girl", "woman"],
+ "keywords": [
+ "female",
+ "girl",
+ "woman"
+ ],
"moji": "💆"
},
+ "massage_tone1": {
+ "unicode": "1F486-1F3FB",
+ "unicode_alternates": "",
+ "name": "face massage tone 1",
+ "shortname": ":massage_tone1:",
+ "category": "people",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": [
+ "female",
+ "girl",
+ "woman"
+ ]
+ },
+ "massage_tone2": {
+ "unicode": "1F486-1F3FC",
+ "unicode_alternates": "",
+ "name": "face massage tone 2",
+ "shortname": ":massage_tone2:",
+ "category": "people",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": [
+ "female",
+ "girl",
+ "woman"
+ ]
+ },
+ "massage_tone3": {
+ "unicode": "1F486-1F3FD",
+ "unicode_alternates": "",
+ "name": "face massage tone 3",
+ "shortname": ":massage_tone3:",
+ "category": "people",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": [
+ "female",
+ "girl",
+ "woman"
+ ]
+ },
+ "massage_tone4": {
+ "unicode": "1F486-1F3FE",
+ "unicode_alternates": "",
+ "name": "face massage tone 4",
+ "shortname": ":massage_tone4:",
+ "category": "people",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": [
+ "female",
+ "girl",
+ "woman"
+ ]
+ },
+ "massage_tone5": {
+ "unicode": "1F486-1F3FF",
+ "unicode_alternates": "",
+ "name": "face massage tone 5",
+ "shortname": ":massage_tone5:",
+ "category": "people",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": [
+ "female",
+ "girl",
+ "woman"
+ ]
+ },
"meat_on_bone": {
"unicode": "1F356",
"unicode_alternates": [],
@@ -8360,7 +17130,14 @@
"category": "objects",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["food", "good", "meat", "bone", "animal", "cooked"],
+ "keywords": [
+ "food",
+ "good",
+ "meat",
+ "bone",
+ "animal",
+ "cooked"
+ ],
"moji": "🍖"
},
"medal": {
@@ -8369,9 +17146,22 @@
"name": "sports medal",
"shortname": ":medal:",
"category": "activity",
- "aliases": [":sports_medal:"],
- "aliases_ascii": [],
- "keywords": ["award", "ceremony", "contest", "ftw", "place", "win", "first", "show", "reward", "achievement"]
+ "aliases": [
+ ":sports_medal:"
+ ],
+ "aliases_ascii": [],
+ "keywords": [
+ "award",
+ "ceremony",
+ "contest",
+ "ftw",
+ "place",
+ "win",
+ "first",
+ "show",
+ "reward",
+ "achievement"
+ ]
},
"mega": {
"unicode": "1F4E3",
@@ -8381,7 +17171,11 @@
"category": "objects",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["sound", "speaker", "volume"],
+ "keywords": [
+ "sound",
+ "speaker",
+ "volume"
+ ],
"moji": "📣"
},
"melon": {
@@ -8392,9 +17186,26 @@
"category": "objects",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["food", "fruit", "nature", "melon", "cantaloupe", "honeydew"],
+ "keywords": [
+ "food",
+ "fruit",
+ "nature",
+ "melon",
+ "cantaloupe",
+ "honeydew"
+ ],
"moji": "🍈"
},
+ "menorah": {
+ "unicode": "1F54E",
+ "unicode_alternates": "",
+ "name": "menorah with nine branches",
+ "shortname": ":menorah:",
+ "category": "symbols",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": []
+ },
"mens": {
"unicode": "1F6B9",
"unicode_alternates": [],
@@ -8403,9 +17214,122 @@
"category": "other",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["restroom", "toilet", "wc", "men", "bathroom", "restroom", "sign", "boy", "male", "avatar"],
+ "keywords": [
+ "restroom",
+ "toilet",
+ "wc",
+ "men",
+ "bathroom",
+ "restroom",
+ "sign",
+ "boy",
+ "male",
+ "avatar"
+ ],
"moji": "🚹"
},
+ "metal": {
+ "unicode": "1F918",
+ "unicode_alternates": "",
+ "name": "sign of the horns",
+ "shortname": ":metal:",
+ "category": "people",
+ "aliases": [
+ ":sign_of_the_horns:"
+ ],
+ "aliases_ascii": [],
+ "keywords": [
+ "band",
+ "concert",
+ "fingers",
+ "rocknroll"
+ ]
+ },
+ "metal_tone1": {
+ "unicode": "1F918-1F3FB",
+ "unicode_alternates": "",
+ "name": "sign of the horns tone 1",
+ "shortname": ":metal_tone1:",
+ "category": "people",
+ "aliases": [
+ ":sign_of_the_horns_tone1:"
+ ],
+ "aliases_ascii": [],
+ "keywords": [
+ "band",
+ "concert",
+ "fingers",
+ "rocknroll"
+ ]
+ },
+ "metal_tone2": {
+ "unicode": "1F918-1F3FC",
+ "unicode_alternates": "",
+ "name": "sign of the horns tone 2",
+ "shortname": ":metal_tone2:",
+ "category": "people",
+ "aliases": [
+ ":sign_of_the_horns_tone2:"
+ ],
+ "aliases_ascii": [],
+ "keywords": [
+ "band",
+ "concert",
+ "fingers",
+ "rocknroll"
+ ]
+ },
+ "metal_tone3": {
+ "unicode": "1F918-1F3FD",
+ "unicode_alternates": "",
+ "name": "sign of the horns tone 3",
+ "shortname": ":metal_tone3:",
+ "category": "people",
+ "aliases": [
+ ":sign_of_the_horns_tone3:"
+ ],
+ "aliases_ascii": [],
+ "keywords": [
+ "band",
+ "concert",
+ "fingers",
+ "rocknroll"
+ ]
+ },
+ "metal_tone4": {
+ "unicode": "1F918-1F3FE",
+ "unicode_alternates": "",
+ "name": "sign of the horns tone 4",
+ "shortname": ":metal_tone4:",
+ "category": "people",
+ "aliases": [
+ ":sign_of_the_horns_tone4:"
+ ],
+ "aliases_ascii": [],
+ "keywords": [
+ "band",
+ "concert",
+ "fingers",
+ "rocknroll"
+ ]
+ },
+ "metal_tone5": {
+ "unicode": "1F918-1F3FF",
+ "unicode_alternates": "",
+ "name": "sign of the horns tone 5",
+ "shortname": ":metal_tone5:",
+ "category": "people",
+ "aliases": [
+ ":sign_of_the_horns_tone5:"
+ ],
+ "aliases_ascii": [],
+ "keywords": [
+ "band",
+ "concert",
+ "fingers",
+ "rocknroll"
+ ]
+ },
"metro": {
"unicode": "1F687",
"unicode_alternates": [],
@@ -8414,7 +17338,17 @@
"category": "places",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["blue-square", "mrt", "transportation", "tube", "underground", "metro", "subway", "underground", "train"],
+ "keywords": [
+ "blue-square",
+ "mrt",
+ "transportation",
+ "tube",
+ "underground",
+ "metro",
+ "subway",
+ "underground",
+ "train"
+ ],
"moji": "🚇"
},
"microphone": {
@@ -8425,7 +17359,17 @@
"category": "objects",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["PA", "music", "sound", "microphone", "mic", "audio", "sound", "voice", "karaoke"],
+ "keywords": [
+ "PA",
+ "music",
+ "sound",
+ "microphone",
+ "mic",
+ "audio",
+ "sound",
+ "voice",
+ "karaoke"
+ ],
"moji": "🎤"
},
"microphone2": {
@@ -8434,9 +17378,15 @@
"name": "studio microphone",
"shortname": ":microphone2:",
"category": "objects_symbols",
- "aliases": [":studio_microphone:"],
+ "aliases": [
+ ":studio_microphone:"
+ ],
"aliases_ascii": [],
- "keywords": ["mic", "audio", "recording"]
+ "keywords": [
+ "mic",
+ "audio",
+ "recording"
+ ]
},
"microscope": {
"unicode": "1F52C",
@@ -8446,7 +17396,11 @@
"category": "objects",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["experiment", "laboratory", "zoomin"],
+ "keywords": [
+ "experiment",
+ "laboratory",
+ "zoomin"
+ ],
"moji": "🔬"
},
"middle_finger": {
@@ -8455,9 +17409,83 @@
"name": "reversed hand with middle finger extended",
"shortname": ":middle_finger:",
"category": "people",
- "aliases": [":reversed_hand_with_middle_finger_extended:"],
+ "aliases": [
+ ":reversed_hand_with_middle_finger_extended:"
+ ],
+ "aliases_ascii": [],
+ "keywords": [
+ "fu"
+ ]
+ },
+ "middle_finger_tone1": {
+ "unicode": "1F595-1F3FB",
+ "unicode_alternates": "",
+ "name": "reversed hand with middle finger extended tone 1",
+ "shortname": ":middle_finger_tone1:",
+ "category": "people",
+ "aliases": [
+ ":reversed_hand_with_middle_finger_extended_tone1:"
+ ],
+ "aliases_ascii": [],
+ "keywords": [
+ "fu"
+ ]
+ },
+ "middle_finger_tone2": {
+ "unicode": "1F595-1F3FC",
+ "unicode_alternates": "",
+ "name": "reversed hand with middle finger extended tone 2",
+ "shortname": ":middle_finger_tone2:",
+ "category": "people",
+ "aliases": [
+ ":reversed_hand_with_middle_finger_extended_tone2:"
+ ],
+ "aliases_ascii": [],
+ "keywords": [
+ "fu"
+ ]
+ },
+ "middle_finger_tone3": {
+ "unicode": "1F595-1F3FD",
+ "unicode_alternates": "",
+ "name": "reversed hand with middle finger extended tone 3",
+ "shortname": ":middle_finger_tone3:",
+ "category": "people",
+ "aliases": [
+ ":reversed_hand_with_middle_finger_extended_tone3:"
+ ],
+ "aliases_ascii": [],
+ "keywords": [
+ "fu"
+ ]
+ },
+ "middle_finger_tone4": {
+ "unicode": "1F595-1F3FE",
+ "unicode_alternates": "",
+ "name": "reversed hand with middle finger extended tone 4",
+ "shortname": ":middle_finger_tone4:",
+ "category": "people",
+ "aliases": [
+ ":reversed_hand_with_middle_finger_extended_tone4:"
+ ],
+ "aliases_ascii": [],
+ "keywords": [
+ "fu"
+ ]
+ },
+ "middle_finger_tone5": {
+ "unicode": "1F595-1F3FF",
+ "unicode_alternates": "",
+ "name": "reversed hand with middle finger extended tone 5",
+ "shortname": ":middle_finger_tone5:",
+ "category": "people",
+ "aliases": [
+ ":reversed_hand_with_middle_finger_extended_tone5:"
+ ],
"aliases_ascii": [],
- "keywords": ["fu"]
+ "keywords": [
+ "fu"
+ ]
},
"military_medal": {
"unicode": "1F396",
@@ -8467,7 +17495,13 @@
"category": "celebration",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["honor", "acknowledgment", "purple heart", "heroism", "veteran"]
+ "keywords": [
+ "honor",
+ "acknowledgment",
+ "purple heart",
+ "heroism",
+ "veteran"
+ ]
},
"milky_way": {
"unicode": "1F30C",
@@ -8477,7 +17511,17 @@
"category": "nature",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["photo", "space", "milky", "galaxy", "star", "stars", "planets", "space", "sky"],
+ "keywords": [
+ "photo",
+ "space",
+ "milky",
+ "galaxy",
+ "star",
+ "stars",
+ "planets",
+ "space",
+ "sky"
+ ],
"moji": "🌌"
},
"minibus": {
@@ -8488,7 +17532,15 @@
"category": "places",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["car", "transportation", "vehicle", "bus", "city", "transport", "transportation"],
+ "keywords": [
+ "car",
+ "transportation",
+ "vehicle",
+ "bus",
+ "city",
+ "transport",
+ "transportation"
+ ],
"moji": "🚐"
},
"minidisc": {
@@ -8499,7 +17551,13 @@
"category": "objects",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["data", "disc", "disk", "record", "technology"],
+ "keywords": [
+ "data",
+ "disc",
+ "disk",
+ "record",
+ "technology"
+ ],
"moji": "💽"
},
"mobile_phone_off": {
@@ -8510,9 +17568,23 @@
"category": "other",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["mute"],
+ "keywords": [
+ "mute"
+ ],
"moji": "📴"
},
+ "money_mouth": {
+ "unicode": "1F911",
+ "unicode_alternates": "",
+ "name": "money-mouth face",
+ "shortname": ":money_mouth:",
+ "category": "people",
+ "aliases": [
+ ":money_mouth_face:"
+ ],
+ "aliases_ascii": [],
+ "keywords": []
+ },
"money_with_wings": {
"unicode": "1F4B8",
"unicode_alternates": [],
@@ -8521,7 +17593,22 @@
"category": "objects",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["bills", "dollar", "payment", "money", "wings", "easy", "spend", "work", "lost", "blown", "burned", "gift", "cash", "dollar"],
+ "keywords": [
+ "bills",
+ "dollar",
+ "payment",
+ "money",
+ "wings",
+ "easy",
+ "spend",
+ "work",
+ "lost",
+ "blown",
+ "burned",
+ "gift",
+ "cash",
+ "dollar"
+ ],
"moji": "💸"
},
"moneybag": {
@@ -8532,7 +17619,11 @@
"category": "objects",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["coins", "dollar", "payment"],
+ "keywords": [
+ "coins",
+ "dollar",
+ "payment"
+ ],
"moji": "💰"
},
"monkey": {
@@ -8543,7 +17634,14 @@
"category": "nature",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["animal", "nature", "monkey", "primate", "banana", "silly"],
+ "keywords": [
+ "animal",
+ "nature",
+ "monkey",
+ "primate",
+ "banana",
+ "silly"
+ ],
"moji": "🐒"
},
"monkey_face": {
@@ -8554,7 +17652,10 @@
"category": "nature",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["animal", "nature"],
+ "keywords": [
+ "animal",
+ "nature"
+ ],
"moji": "🐵"
},
"monorail": {
@@ -8565,7 +17666,14 @@
"category": "places",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["transportation", "vehicle", "train", "mono", "rail", "transport"],
+ "keywords": [
+ "transportation",
+ "vehicle",
+ "train",
+ "mono",
+ "rail",
+ "transport"
+ ],
"moji": "🚝"
},
"mood_bubble": {
@@ -8576,7 +17684,13 @@
"category": "objects_symbols",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["balloon", "conversation", "communication", "comic", "feeling"]
+ "keywords": [
+ "balloon",
+ "conversation",
+ "communication",
+ "comic",
+ "feeling"
+ ]
},
"mood_bubble_lightning": {
"unicode": "1F5F1",
@@ -8584,9 +17698,17 @@
"name": "lightning mood bubble",
"shortname": ":mood_bubble_lightning:",
"category": "objects_symbols",
- "aliases": [":lightning_mood_bubble:"],
- "aliases_ascii": [],
- "keywords": ["balloon", "conversation", "communication", "comic", "feeling"]
+ "aliases": [
+ ":lightning_mood_bubble:"
+ ],
+ "aliases_ascii": [],
+ "keywords": [
+ "balloon",
+ "conversation",
+ "communication",
+ "comic",
+ "feeling"
+ ]
},
"mood_lightning": {
"unicode": "1F5F2",
@@ -8594,9 +17716,15 @@
"name": "lightning mood",
"shortname": ":mood_lightning:",
"category": "objects_symbols",
- "aliases": [":lightning_mood:"],
+ "aliases": [
+ ":lightning_mood:"
+ ],
"aliases_ascii": [],
- "keywords": ["zap", "electric", "current"]
+ "keywords": [
+ "zap",
+ "electric",
+ "current"
+ ]
},
"mortar_board": {
"unicode": "1F393",
@@ -8606,9 +17734,35 @@
"category": "objects",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["cap", "college", "degree", "graduation", "hat", "school", "university", "graduation", "cap", "mortarboard", "academic", "education", "ceremony", "square", "tassel"],
+ "keywords": [
+ "cap",
+ "college",
+ "degree",
+ "graduation",
+ "hat",
+ "school",
+ "university",
+ "graduation",
+ "cap",
+ "mortarboard",
+ "academic",
+ "education",
+ "ceremony",
+ "square",
+ "tassel"
+ ],
"moji": "🎓"
},
+ "mosque": {
+ "unicode": "1F54C",
+ "unicode_alternates": "",
+ "name": "mosque",
+ "shortname": ":mosque:",
+ "category": "travel",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": []
+ },
"motorboat": {
"unicode": "1F6E5",
"unicode_alternates": [],
@@ -8617,7 +17771,13 @@
"category": "travel_places",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["transportation", "vehicle", "boat", "speedboat", "powerboat"]
+ "keywords": [
+ "transportation",
+ "vehicle",
+ "boat",
+ "speedboat",
+ "powerboat"
+ ]
},
"motorcycle": {
"unicode": "1F3CD",
@@ -8625,9 +17785,14 @@
"name": "racing motorcycle",
"shortname": ":motorcycle:",
"category": "activity",
- "aliases": [":racing_motorcycle:"],
+ "aliases": [
+ ":racing_motorcycle:"
+ ],
"aliases_ascii": [],
- "keywords": ["bike", "speed"]
+ "keywords": [
+ "bike",
+ "speed"
+ ]
},
"motorway": {
"unicode": "1F6E3",
@@ -8637,7 +17802,13 @@
"category": "travel_places",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["road", "highway", "freeway", "traffic", "travel"]
+ "keywords": [
+ "road",
+ "highway",
+ "freeway",
+ "traffic",
+ "travel"
+ ]
},
"mount_fuji": {
"unicode": "1F5FB",
@@ -8647,9 +17818,26 @@
"category": "places",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["japan", "mountain", "nature", "photo"],
+ "keywords": [
+ "japan",
+ "mountain",
+ "nature",
+ "photo"
+ ],
"moji": "🗻"
},
+ "mountain": {
+ "unicode": "26F0",
+ "unicode_alternates": "",
+ "name": "mountain",
+ "shortname": ":mountain:",
+ "category": "travel",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": [
+ "place"
+ ]
+ },
"mountain_bicyclist": {
"unicode": "1F6B5",
"unicode_alternates": [],
@@ -8658,9 +17846,104 @@
"category": "objects",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["human", "sports", "transportation", "bicyclist", "mountain", "bike", "pedal", "bicycle", "transportation"],
+ "keywords": [
+ "human",
+ "sports",
+ "transportation",
+ "bicyclist",
+ "mountain",
+ "bike",
+ "pedal",
+ "bicycle",
+ "transportation"
+ ],
"moji": "🚵"
},
+ "mountain_bicyclist_tone1": {
+ "unicode": "1F6B5-1F3FB",
+ "unicode_alternates": "",
+ "name": "mountain bicyclist tone 1",
+ "shortname": ":mountain_bicyclist_tone1:",
+ "category": "activity",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": [
+ "sport",
+ "transportation",
+ "bike",
+ "pedal",
+ "bicycle",
+ "transportation"
+ ]
+ },
+ "mountain_bicyclist_tone2": {
+ "unicode": "1F6B5-1F3FC",
+ "unicode_alternates": "",
+ "name": "mountain bicyclist tone 2",
+ "shortname": ":mountain_bicyclist_tone2:",
+ "category": "activity",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": [
+ "sport",
+ "transportation",
+ "bike",
+ "pedal",
+ "bicycle",
+ "transportation"
+ ]
+ },
+ "mountain_bicyclist_tone3": {
+ "unicode": "1F6B5-1F3FD",
+ "unicode_alternates": "",
+ "name": "mountain bicyclist tone 3",
+ "shortname": ":mountain_bicyclist_tone3:",
+ "category": "activity",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": [
+ "sport",
+ "transportation",
+ "bike",
+ "pedal",
+ "bicycle",
+ "transportation"
+ ]
+ },
+ "mountain_bicyclist_tone4": {
+ "unicode": "1F6B5-1F3FE",
+ "unicode_alternates": "",
+ "name": "mountain bicyclist tone 4",
+ "shortname": ":mountain_bicyclist_tone4:",
+ "category": "activity",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": [
+ "sport",
+ "transportation",
+ "bike",
+ "pedal",
+ "bicycle",
+ "transportation"
+ ]
+ },
+ "mountain_bicyclist_tone5": {
+ "unicode": "1F6B5-1F3FF",
+ "unicode_alternates": "",
+ "name": "mountain bicyclist tone 5",
+ "shortname": ":mountain_bicyclist_tone5:",
+ "category": "activity",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": [
+ "sport",
+ "transportation",
+ "bike",
+ "pedal",
+ "bicycle",
+ "transportation"
+ ]
+ },
"mountain_cableway": {
"unicode": "1F6A0",
"unicode_alternates": [],
@@ -8669,7 +17952,15 @@
"category": "places",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["transportation", "vehicle", "mountain", "cable", "rail", "train", "railway"],
+ "keywords": [
+ "transportation",
+ "vehicle",
+ "mountain",
+ "cable",
+ "rail",
+ "train",
+ "railway"
+ ],
"moji": "🚠"
},
"mountain_railway": {
@@ -8680,7 +17971,14 @@
"category": "places",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["transportation", "mountain", "railway", "rail", "train", "transport"],
+ "keywords": [
+ "transportation",
+ "mountain",
+ "railway",
+ "rail",
+ "train",
+ "transport"
+ ],
"moji": "🚞"
},
"mountain_snow": {
@@ -8689,9 +17987,16 @@
"name": "snow capped mountain",
"shortname": ":mountain_snow:",
"category": "travel_places",
- "aliases": [":snow_capped_mountain:"],
+ "aliases": [
+ ":snow_capped_mountain:"
+ ],
"aliases_ascii": [],
- "keywords": ["cold", "elevation", "hiking", "peak"]
+ "keywords": [
+ "cold",
+ "elevation",
+ "hiking",
+ "peak"
+ ]
},
"mouse": {
"unicode": "1F42D",
@@ -8701,7 +18006,10 @@
"category": "nature",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["animal", "nature"],
+ "keywords": [
+ "animal",
+ "nature"
+ ],
"moji": "🐭"
},
"mouse2": {
@@ -8712,7 +18020,13 @@
"category": "nature",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["animal", "nature", "mouse", "mice", "rodent"],
+ "keywords": [
+ "animal",
+ "nature",
+ "mouse",
+ "mice",
+ "rodent"
+ ],
"moji": "🐁"
},
"mouse_one": {
@@ -8721,9 +18035,32 @@
"name": "one button mouse",
"shortname": ":mouse_one:",
"category": "objects_symbols",
- "aliases": [":one_button_mouse:"],
+ "aliases": [
+ ":one_button_mouse:"
+ ],
+ "aliases_ascii": [],
+ "keywords": [
+ "computer",
+ "input",
+ "device"
+ ]
+ },
+ "mouse_three_button": {
+ "unicode": "1F5B1",
+ "unicode_alternates": "",
+ "name": "three button mouse",
+ "shortname": ":mouse_three_button:",
+ "category": "objects",
+ "aliases": [
+ ":three_button_mouse:"
+ ],
"aliases_ascii": [],
- "keywords": ["computer", "input", "device"]
+ "keywords": [
+ "3",
+ "computer",
+ "object",
+ "office"
+ ]
},
"movie_camera": {
"unicode": "1F3A5",
@@ -8733,7 +18070,16 @@
"category": "objects",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["film", "record", "movie", "camera", "camcorder", "video", "motion", "picture"],
+ "keywords": [
+ "film",
+ "record",
+ "movie",
+ "camera",
+ "camcorder",
+ "video",
+ "motion",
+ "picture"
+ ],
"moji": "🎥"
},
"moyai": {
@@ -8744,7 +18090,10 @@
"category": "places",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["island", "stone"],
+ "keywords": [
+ "island",
+ "stone"
+ ],
"moji": "🗿"
},
"muscle": {
@@ -8755,9 +18104,101 @@
"category": "emoticons",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["arm", "flex", "hand", "strong", "muscle", "bicep"],
+ "keywords": [
+ "arm",
+ "flex",
+ "hand",
+ "strong",
+ "muscle",
+ "bicep"
+ ],
"moji": "💪"
},
+ "muscle_tone1": {
+ "unicode": "1F4AA-1F3FB",
+ "unicode_alternates": "",
+ "name": "flexed biceps tone 1",
+ "shortname": ":muscle_tone1:",
+ "category": "people",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": [
+ "arm",
+ "flex",
+ "hand",
+ "strong",
+ "muscle",
+ "bicep"
+ ]
+ },
+ "muscle_tone2": {
+ "unicode": "1F4AA-1F3FC",
+ "unicode_alternates": "",
+ "name": "flexed biceps tone 2",
+ "shortname": ":muscle_tone2:",
+ "category": "people",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": [
+ "arm",
+ "flex",
+ "hand",
+ "strong",
+ "muscle",
+ "bicep"
+ ]
+ },
+ "muscle_tone3": {
+ "unicode": "1F4AA-1F3FD",
+ "unicode_alternates": "",
+ "name": "flexed biceps tone 3",
+ "shortname": ":muscle_tone3:",
+ "category": "people",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": [
+ "arm",
+ "flex",
+ "hand",
+ "strong",
+ "muscle",
+ "bicep"
+ ]
+ },
+ "muscle_tone4": {
+ "unicode": "1F4AA-1F3FE",
+ "unicode_alternates": "",
+ "name": "flexed biceps tone 4",
+ "shortname": ":muscle_tone4:",
+ "category": "people",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": [
+ "arm",
+ "flex",
+ "hand",
+ "strong",
+ "muscle",
+ "bicep"
+ ]
+ },
+ "muscle_tone5": {
+ "unicode": "1F4AA-1F3FF",
+ "unicode_alternates": "",
+ "name": "flexed biceps tone 5",
+ "shortname": ":muscle_tone5:",
+ "category": "people",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": [
+ "arm",
+ "flex",
+ "hand",
+ "strong",
+ "muscle",
+ "bicep"
+ ]
+ },
"mushroom": {
"unicode": "1F344",
"unicode_alternates": [],
@@ -8766,7 +18207,14 @@
"category": "nature",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["plant", "vegetable", "mushroom", "fungi", "food", "fungus"],
+ "keywords": [
+ "plant",
+ "vegetable",
+ "mushroom",
+ "fungi",
+ "food",
+ "fungus"
+ ],
"moji": "🍄"
},
"musical_keyboard": {
@@ -8777,7 +18225,16 @@
"category": "objects",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["instrument", "piano", "music", "keyboard", "piano", "organ", "instrument", "electric"],
+ "keywords": [
+ "instrument",
+ "piano",
+ "music",
+ "keyboard",
+ "piano",
+ "organ",
+ "instrument",
+ "electric"
+ ],
"moji": "🎹"
},
"musical_note": {
@@ -8788,7 +18245,14 @@
"category": "objects",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["score", "musical", "music", "note", "music", "sound"],
+ "keywords": [
+ "score",
+ "musical",
+ "music",
+ "note",
+ "music",
+ "sound"
+ ],
"moji": "🎵"
},
"musical_score": {
@@ -8799,7 +18263,17 @@
"category": "objects",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["clef", "treble", "music", "musical", "score", "clef", "g-clef", "stave", "staff"],
+ "keywords": [
+ "clef",
+ "treble",
+ "music",
+ "musical",
+ "score",
+ "clef",
+ "g-clef",
+ "stave",
+ "staff"
+ ],
"moji": "🎼"
},
"mute": {
@@ -8810,7 +18284,10 @@
"category": "objects",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["sound", "volume"],
+ "keywords": [
+ "sound",
+ "volume"
+ ],
"moji": "🔇"
},
"nail_care": {
@@ -8821,9 +18298,77 @@
"category": "emoticons",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["beauty", "manicure"],
+ "keywords": [
+ "beauty",
+ "manicure"
+ ],
"moji": "💅"
},
+ "nail_care_tone1": {
+ "unicode": "1F485-1F3FB",
+ "unicode_alternates": "",
+ "name": "nail polish tone 1",
+ "shortname": ":nail_care_tone1:",
+ "category": "people",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": [
+ "beauty",
+ "manicure"
+ ]
+ },
+ "nail_care_tone2": {
+ "unicode": "1F485-1F3FC",
+ "unicode_alternates": "",
+ "name": "nail polish tone 2",
+ "shortname": ":nail_care_tone2:",
+ "category": "people",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": [
+ "beauty",
+ "manicure"
+ ]
+ },
+ "nail_care_tone3": {
+ "unicode": "1F485-1F3FD",
+ "unicode_alternates": "",
+ "name": "nail polish tone 3",
+ "shortname": ":nail_care_tone3:",
+ "category": "people",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": [
+ "beauty",
+ "manicure"
+ ]
+ },
+ "nail_care_tone4": {
+ "unicode": "1F485-1F3FE",
+ "unicode_alternates": "",
+ "name": "nail polish tone 4",
+ "shortname": ":nail_care_tone4:",
+ "category": "people",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": [
+ "beauty",
+ "manicure"
+ ]
+ },
+ "nail_care_tone5": {
+ "unicode": "1F485-1F3FF",
+ "unicode_alternates": "",
+ "name": "nail polish tone 5",
+ "shortname": ":nail_care_tone5:",
+ "category": "people",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": [
+ "beauty",
+ "manicure"
+ ]
+ },
"name_badge": {
"unicode": "1F4DB",
"unicode_alternates": [],
@@ -8832,7 +18377,10 @@
"category": "objects",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["fire", "forbid"],
+ "keywords": [
+ "fire",
+ "forbid"
+ ],
"moji": "📛"
},
"necktie": {
@@ -8843,7 +18391,13 @@
"category": "emoticons",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["cloth", "fashion", "formal", "shirt", "suitup"],
+ "keywords": [
+ "cloth",
+ "fashion",
+ "formal",
+ "shirt",
+ "suitup"
+ ],
"moji": "👔"
},
"negative_squared_cross_mark": {
@@ -8854,18 +18408,42 @@
"category": "other",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["deny", "green-square", "no", "x"],
+ "keywords": [
+ "deny",
+ "green-square",
+ "no",
+ "x"
+ ],
"moji": "❎"
},
+ "nerd": {
+ "unicode": "1F913",
+ "unicode_alternates": "",
+ "name": "nerd face",
+ "shortname": ":nerd:",
+ "category": "people",
+ "aliases": [
+ ":nerd_face:"
+ ],
+ "aliases_ascii": [],
+ "keywords": []
+ },
"network": {
"unicode": "1F5A7",
"unicode_alternates": [],
"name": "three networked computers",
"shortname": ":network:",
"category": "objects_symbols",
- "aliases": [":three_networked_computers:"],
+ "aliases": [
+ ":three_networked_computers:"
+ ],
"aliases_ascii": [],
- "keywords": ["lan", "wan", "network", "technology"]
+ "keywords": [
+ "lan",
+ "wan",
+ "network",
+ "technology"
+ ]
},
"neutral_face": {
"unicode": "1F610",
@@ -8875,7 +18453,14 @@
"category": "emoticons",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["face", "indifference", "neutral", "objective", "impartial", "blank"],
+ "keywords": [
+ "face",
+ "indifference",
+ "neutral",
+ "objective",
+ "impartial",
+ "blank"
+ ],
"moji": "😐"
},
"new": {
@@ -8886,7 +18471,9 @@
"category": "other",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["blue-square"],
+ "keywords": [
+ "blue-square"
+ ],
"moji": "🆕"
},
"new_moon": {
@@ -8897,7 +18484,15 @@
"category": "nature",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["nature", "moon", "new", "sky", "night", "cheese", "phase"],
+ "keywords": [
+ "nature",
+ "moon",
+ "new",
+ "sky",
+ "night",
+ "cheese",
+ "phase"
+ ],
"moji": "🌑"
},
"new_moon_with_face": {
@@ -8908,7 +18503,17 @@
"category": "nature",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["nature", "moon", "new", "anthropomorphic", "face", "sky", "night", "cheese", "phase"],
+ "keywords": [
+ "nature",
+ "moon",
+ "new",
+ "anthropomorphic",
+ "face",
+ "sky",
+ "night",
+ "cheese",
+ "phase"
+ ],
"moji": "🌚"
},
"newspaper": {
@@ -8919,7 +18524,10 @@
"category": "objects",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["headline", "press"],
+ "keywords": [
+ "headline",
+ "press"
+ ],
"moji": "📰"
},
"newspaper2": {
@@ -8928,9 +18536,29 @@
"name": "rolled-up newspaper",
"shortname": ":newspaper2:",
"category": "objects_symbols",
- "aliases": [":rolled_up_newspaper:"],
- "aliases_ascii": [],
- "keywords": ["headline", "press"]
+ "aliases": [
+ ":rolled_up_newspaper:"
+ ],
+ "aliases_ascii": [],
+ "keywords": [
+ "headline",
+ "press"
+ ]
+ },
+ "ng": {
+ "unicode": "1F196",
+ "unicode_alternates": "",
+ "name": "squared ng",
+ "shortname": ":ng:",
+ "category": "symbols",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": [
+ "blue-square",
+ "no good",
+ "symbol",
+ "word"
+ ]
},
"night_with_stars": {
"unicode": "1F303",
@@ -8940,19 +18568,33 @@
"category": "places",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["night", "star", "cloudless", "evening", "planets", "space", "sky"],
+ "keywords": [
+ "night",
+ "star",
+ "cloudless",
+ "evening",
+ "planets",
+ "space",
+ "sky"
+ ],
"moji": "🌃"
},
"nine": {
"moji": "9️⃣",
"unicode": "0039-20E3",
- "unicode_alternates": ["0039-FE0F-20E3"],
+ "unicode_alternates": [
+ "0039-FE0F-20E3"
+ ],
"name": "digit nine",
"shortname": ":nine:",
"category": "other",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["9", "blue-square", "numbers"]
+ "keywords": [
+ "9",
+ "blue-square",
+ "numbers"
+ ]
},
"no_bell": {
"unicode": "1F515",
@@ -8962,7 +18604,11 @@
"category": "objects",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["mute", "sound", "volume"],
+ "keywords": [
+ "mute",
+ "sound",
+ "volume"
+ ],
"moji": "🔕"
},
"no_bicycles": {
@@ -8973,18 +18619,33 @@
"category": "other",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["cyclist", "prohibited", "bicycle", "bike pedal", "no"],
+ "keywords": [
+ "cyclist",
+ "prohibited",
+ "bicycle",
+ "bike pedal",
+ "no"
+ ],
"moji": "🚳"
},
"no_entry": {
"unicode": "26D4",
- "unicode_alternates": ["26D4-FE0F"],
+ "unicode_alternates": [
+ "26D4-FE0F"
+ ],
"name": "no entry",
"shortname": ":no_entry:",
"category": "other",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["bad", "denied", "limit", "privacy", "security", "stop"],
+ "keywords": [
+ "bad",
+ "denied",
+ "limit",
+ "privacy",
+ "security",
+ "stop"
+ ],
"moji": "⛔"
},
"no_entry_sign": {
@@ -8995,7 +18656,16 @@
"category": "other",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["denied", "disallow", "forbid", "limit", "stop", "no", "stop", "entry"],
+ "keywords": [
+ "denied",
+ "disallow",
+ "forbid",
+ "limit",
+ "stop",
+ "no",
+ "stop",
+ "entry"
+ ],
"moji": "🚫"
},
"no_good": {
@@ -9006,9 +18676,128 @@
"category": "emoticons",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["female", "girl", "woman", "no", "stop", "nope", "don&#039;t", "not"],
+ "keywords": [
+ "female",
+ "girl",
+ "woman",
+ "no",
+ "stop",
+ "nope",
+ "don&#039;t",
+ "not"
+ ],
"moji": "🙅"
},
+ "no_good_tone1": {
+ "unicode": "1F645-1F3FB",
+ "unicode_alternates": "",
+ "name": "face with no good gesture tone 1",
+ "shortname": ":no_good_tone1:",
+ "category": "people",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": [
+ "female",
+ "girl",
+ "woman",
+ "stop",
+ "nope",
+ "don't",
+ "not",
+ "forbidden",
+ "hand",
+ "person",
+ "prohibited"
+ ]
+ },
+ "no_good_tone2": {
+ "unicode": "1F645-1F3FC",
+ "unicode_alternates": "",
+ "name": "face with no good gesture tone 2",
+ "shortname": ":no_good_tone2:",
+ "category": "people",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": [
+ "female",
+ "girl",
+ "woman",
+ "stop",
+ "nope",
+ "don't",
+ "not",
+ "forbidden",
+ "hand",
+ "person",
+ "prohibited"
+ ]
+ },
+ "no_good_tone3": {
+ "unicode": "1F645-1F3FD",
+ "unicode_alternates": "",
+ "name": "face with no good gesture tone 3",
+ "shortname": ":no_good_tone3:",
+ "category": "people",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": [
+ "female",
+ "girl",
+ "woman",
+ "stop",
+ "nope",
+ "don't",
+ "not",
+ "forbidden",
+ "hand",
+ "person",
+ "prohibited"
+ ]
+ },
+ "no_good_tone4": {
+ "unicode": "1F645-1F3FE",
+ "unicode_alternates": "",
+ "name": "face with no good gesture tone 4",
+ "shortname": ":no_good_tone4:",
+ "category": "people",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": [
+ "female",
+ "girl",
+ "woman",
+ "stop",
+ "nope",
+ "don't",
+ "not",
+ "forbidden",
+ "hand",
+ "person",
+ "prohibited"
+ ]
+ },
+ "no_good_tone5": {
+ "unicode": "1F645-1F3FF",
+ "unicode_alternates": "",
+ "name": "face with no good gesture tone 5",
+ "shortname": ":no_good_tone5:",
+ "category": "people",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": [
+ "female",
+ "girl",
+ "woman",
+ "stop",
+ "nope",
+ "don't",
+ "not",
+ "forbidden",
+ "hand",
+ "person",
+ "prohibited"
+ ]
+ },
"no_mobile_phones": {
"unicode": "1F4F5",
"unicode_alternates": [],
@@ -9017,7 +18806,10 @@
"category": "other",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["iphone", "mute"],
+ "keywords": [
+ "iphone",
+ "mute"
+ ],
"moji": "📵"
},
"no_mouth": {
@@ -9027,8 +18819,24 @@
"shortname": ":no_mouth:",
"category": "emoticons",
"aliases": [],
- "aliases_ascii": [":-X", ":X", ":-#", ":#", "=X", "=x", ":x", ":-x", "=#"],
- "keywords": ["face", "hellokitty", "mouth", "silent", "vapid"],
+ "aliases_ascii": [
+ ":-X",
+ ":X",
+ ":-#",
+ ":#",
+ "=X",
+ "=x",
+ ":x",
+ ":-x",
+ "=#"
+ ],
+ "keywords": [
+ "face",
+ "hellokitty",
+ "mouth",
+ "silent",
+ "vapid"
+ ],
"moji": "😶"
},
"no_pedestrians": {
@@ -9039,7 +18847,18 @@
"category": "other",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["crossing", "rules", "walking", "no", "walk", "pedestrian", "stroll", "stride", "foot", "feet"],
+ "keywords": [
+ "crossing",
+ "rules",
+ "walking",
+ "no",
+ "walk",
+ "pedestrian",
+ "stroll",
+ "stride",
+ "foot",
+ "feet"
+ ],
"moji": "🚷"
},
"no_smoking": {
@@ -9050,7 +18869,18 @@
"category": "other",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["cigarette", "no", "smoking", "cigarette", "smoke", "cancer", "lungs", "inhale", "tar", "nicotine"],
+ "keywords": [
+ "cigarette",
+ "no",
+ "smoking",
+ "cigarette",
+ "smoke",
+ "cancer",
+ "lungs",
+ "inhale",
+ "tar",
+ "nicotine"
+ ],
"moji": "🚭"
},
"non-potable_water": {
@@ -9061,7 +18891,18 @@
"category": "other",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["drink", "faucet", "tap", "non-potable", "water", "not drinkable", "dirty", "gross", "aqua", "h20"],
+ "keywords": [
+ "drink",
+ "faucet",
+ "tap",
+ "non-potable",
+ "water",
+ "not drinkable",
+ "dirty",
+ "gross",
+ "aqua",
+ "h20"
+ ],
"moji": "🚱"
},
"nose": {
@@ -9072,18 +18913,91 @@
"category": "emoticons",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["smell", "sniff"],
+ "keywords": [
+ "smell",
+ "sniff"
+ ],
"moji": "👃"
},
+ "nose_tone1": {
+ "unicode": "1F443-1F3FB",
+ "unicode_alternates": "",
+ "name": "nose tone 1",
+ "shortname": ":nose_tone1:",
+ "category": "people",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": [
+ "smell",
+ "sniff"
+ ]
+ },
+ "nose_tone2": {
+ "unicode": "1F443-1F3FC",
+ "unicode_alternates": "",
+ "name": "nose tone 2",
+ "shortname": ":nose_tone2:",
+ "category": "people",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": [
+ "smell",
+ "sniff"
+ ]
+ },
+ "nose_tone3": {
+ "unicode": "1F443-1F3FD",
+ "unicode_alternates": "",
+ "name": "nose tone 3",
+ "shortname": ":nose_tone3:",
+ "category": "people",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": [
+ "smell",
+ "sniff"
+ ]
+ },
+ "nose_tone4": {
+ "unicode": "1F443-1F3FE",
+ "unicode_alternates": "",
+ "name": "nose tone 4",
+ "shortname": ":nose_tone4:",
+ "category": "people",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": [
+ "smell",
+ "sniff"
+ ]
+ },
+ "nose_tone5": {
+ "unicode": "1F443-1F3FF",
+ "unicode_alternates": "",
+ "name": "nose tone 5",
+ "shortname": ":nose_tone5:",
+ "category": "people",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": [
+ "smell",
+ "sniff"
+ ]
+ },
"note": {
"unicode": "1F5C9",
"unicode_alternates": [],
"name": "note page",
"shortname": ":note:",
"category": "objects_symbols",
- "aliases": [":note_page:"],
+ "aliases": [
+ ":note_page:"
+ ],
"aliases_ascii": [],
- "keywords": ["stationery", "post-it"]
+ "keywords": [
+ "stationery",
+ "post-it"
+ ]
},
"note_empty": {
"unicode": "1F5C6",
@@ -9091,9 +19005,14 @@
"name": "empty note page",
"shortname": ":note_empty:",
"category": "objects_symbols",
- "aliases": [":empty_note_page:"],
+ "aliases": [
+ ":empty_note_page:"
+ ],
"aliases_ascii": [],
- "keywords": ["stationery", "post-it"]
+ "keywords": [
+ "stationery",
+ "post-it"
+ ]
},
"notebook": {
"unicode": "1F4D3",
@@ -9103,7 +19022,12 @@
"category": "objects",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["notes", "paper", "record", "stationery"],
+ "keywords": [
+ "notes",
+ "paper",
+ "record",
+ "stationery"
+ ],
"moji": "📓"
},
"notebook_with_decorative_cover": {
@@ -9114,7 +19038,12 @@
"category": "objects",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["classroom", "notes", "paper", "record"],
+ "keywords": [
+ "classroom",
+ "notes",
+ "paper",
+ "record"
+ ],
"moji": "📔"
},
"notepad": {
@@ -9123,9 +19052,14 @@
"name": "note pad",
"shortname": ":notepad:",
"category": "objects_symbols",
- "aliases": [":note_pad:"],
+ "aliases": [
+ ":note_pad:"
+ ],
"aliases_ascii": [],
- "keywords": ["stationery", "post-it"]
+ "keywords": [
+ "stationery",
+ "post-it"
+ ]
},
"notepad_empty": {
"unicode": "1F5C7",
@@ -9133,9 +19067,14 @@
"name": "empty note pad",
"shortname": ":notepad_empty:",
"category": "objects_symbols",
- "aliases": [":empty_note_pad:"],
+ "aliases": [
+ ":empty_note_pad:"
+ ],
"aliases_ascii": [],
- "keywords": ["stationery", "post-it"]
+ "keywords": [
+ "stationery",
+ "post-it"
+ ]
},
"notepad_spiral": {
"unicode": "1F5D2",
@@ -9143,9 +19082,13 @@
"name": "spiral note pad",
"shortname": ":notepad_spiral:",
"category": "objects_symbols",
- "aliases": [":spiral_note_pad:"],
+ "aliases": [
+ ":spiral_note_pad:"
+ ],
"aliases_ascii": [],
- "keywords": ["stationery"]
+ "keywords": [
+ "stationery"
+ ]
},
"notes": {
"unicode": "1F3B6",
@@ -9155,7 +19098,16 @@
"category": "objects",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["music", "score", "musical", "music", "notes", "music", "sound", "melody"],
+ "keywords": [
+ "music",
+ "score",
+ "musical",
+ "music",
+ "notes",
+ "music",
+ "sound",
+ "melody"
+ ],
"moji": "🎶"
},
"nut_and_bolt": {
@@ -9166,18 +19118,26 @@
"category": "objects",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["handy", "tools"],
+ "keywords": [
+ "handy",
+ "tools"
+ ],
"moji": "🔩"
},
"o": {
"unicode": "2B55",
- "unicode_alternates": ["2B55-FE0F"],
+ "unicode_alternates": [
+ "2B55-FE0F"
+ ],
"name": "heavy large circle",
"shortname": ":o:",
"category": "other",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["circle", "round"],
+ "keywords": [
+ "circle",
+ "round"
+ ],
"moji": "⭕"
},
"o2": {
@@ -9188,7 +19148,11 @@
"category": "other",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["alphabet", "letter", "red-square"],
+ "keywords": [
+ "alphabet",
+ "letter",
+ "red-square"
+ ],
"moji": "🅾"
},
"ocean": {
@@ -9199,7 +19163,16 @@
"category": "nature",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["sea", "water", "wave", "ocean", "wave", "surf", "beach", "tide"],
+ "keywords": [
+ "sea",
+ "water",
+ "wave",
+ "ocean",
+ "wave",
+ "surf",
+ "beach",
+ "tide"
+ ],
"moji": "🌊"
},
"octopus": {
@@ -9210,7 +19183,12 @@
"category": "nature",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["animal", "creature", "ocean", "sea"],
+ "keywords": [
+ "animal",
+ "creature",
+ "ocean",
+ "sea"
+ ],
"moji": "🐙"
},
"oden": {
@@ -9221,7 +19199,14 @@
"category": "objects",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["food", "japanese", "oden", "seafood", "casserole", "stew"],
+ "keywords": [
+ "food",
+ "japanese",
+ "oden",
+ "seafood",
+ "casserole",
+ "stew"
+ ],
"moji": "🍢"
},
"office": {
@@ -9232,7 +19217,11 @@
"category": "places",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["building", "bureau", "work"],
+ "keywords": [
+ "building",
+ "bureau",
+ "work"
+ ],
"moji": "🏢"
},
"oil": {
@@ -9241,9 +19230,13 @@
"name": "oil drum",
"shortname": ":oil:",
"category": "objects_symbols",
- "aliases": [":oil_drum:"],
+ "aliases": [
+ ":oil_drum:"
+ ],
"aliases_ascii": [],
- "keywords": ["petroleum"]
+ "keywords": [
+ "petroleum"
+ ]
},
"ok": {
"unicode": "1F197",
@@ -9253,7 +19246,12 @@
"category": "other",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["agree", "blue-square", "good", "yes"],
+ "keywords": [
+ "agree",
+ "blue-square",
+ "good",
+ "yes"
+ ],
"moji": "🆗"
},
"ok_hand": {
@@ -9264,9 +19262,126 @@
"category": "emoticons",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["fingers", "limbs", "perfect", "okay", "ok", "smoke", "smoking", "marijuana", "joint", "pot", "420"],
+ "keywords": [
+ "fingers",
+ "limbs",
+ "perfect",
+ "okay",
+ "ok",
+ "smoke",
+ "smoking",
+ "marijuana",
+ "joint",
+ "pot",
+ "420"
+ ],
"moji": "👌"
},
+ "ok_hand_tone1": {
+ "unicode": "1F44C-1F3FB",
+ "unicode_alternates": "",
+ "name": "ok hand sign tone 1",
+ "shortname": ":ok_hand_tone1:",
+ "category": "people",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": [
+ "fingers",
+ "limbs",
+ "perfect",
+ "okay",
+ "smoke",
+ "smoking",
+ "marijuana",
+ "joint",
+ "pot",
+ "420"
+ ]
+ },
+ "ok_hand_tone2": {
+ "unicode": "1F44C-1F3FC",
+ "unicode_alternates": "",
+ "name": "ok hand sign tone 2",
+ "shortname": ":ok_hand_tone2:",
+ "category": "people",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": [
+ "fingers",
+ "limbs",
+ "perfect",
+ "okay",
+ "smoke",
+ "smoking",
+ "marijuana",
+ "joint",
+ "pot",
+ "420"
+ ]
+ },
+ "ok_hand_tone3": {
+ "unicode": "1F44C-1F3FD",
+ "unicode_alternates": "",
+ "name": "ok hand sign tone 3",
+ "shortname": ":ok_hand_tone3:",
+ "category": "people",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": [
+ "fingers",
+ "limbs",
+ "perfect",
+ "okay",
+ "smoke",
+ "smoking",
+ "marijuana",
+ "joint",
+ "pot",
+ "420"
+ ]
+ },
+ "ok_hand_tone4": {
+ "unicode": "1F44C-1F3FE",
+ "unicode_alternates": "",
+ "name": "ok hand sign tone 4",
+ "shortname": ":ok_hand_tone4:",
+ "category": "people",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": [
+ "fingers",
+ "limbs",
+ "perfect",
+ "okay",
+ "smoke",
+ "smoking",
+ "marijuana",
+ "joint",
+ "pot",
+ "420"
+ ]
+ },
+ "ok_hand_tone5": {
+ "unicode": "1F44C-1F3FF",
+ "unicode_alternates": "",
+ "name": "ok hand sign tone 5",
+ "shortname": ":ok_hand_tone5:",
+ "category": "people",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": [
+ "fingers",
+ "limbs",
+ "perfect",
+ "okay",
+ "smoke",
+ "smoking",
+ "marijuana",
+ "joint",
+ "pot",
+ "420"
+ ]
+ },
"ok_woman": {
"unicode": "1F646",
"unicode_alternates": [],
@@ -9274,10 +19389,120 @@
"shortname": ":ok_woman:",
"category": "emoticons",
"aliases": [],
- "aliases_ascii": ["*\\0/*", "\\0/", "*\\O/*", "\\O/"],
- "keywords": ["female", "girl", "human", "pink", "women", "yes", "ok", "okay", "accept"],
+ "aliases_ascii": [
+ "*\\0/*",
+ "\\0/",
+ "*\\O/*",
+ "\\O/"
+ ],
+ "keywords": [
+ "female",
+ "girl",
+ "human",
+ "pink",
+ "women",
+ "yes",
+ "ok",
+ "okay",
+ "accept"
+ ],
"moji": "🙆"
},
+ "ok_woman_tone1": {
+ "unicode": "1F646-1F3FB",
+ "unicode_alternates": "",
+ "name": "face with ok gesture tone1",
+ "shortname": ":ok_woman_tone1:",
+ "category": "people",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": [
+ "female",
+ "girl",
+ "human",
+ "pink",
+ "women",
+ "yes",
+ "okay",
+ "accept"
+ ]
+ },
+ "ok_woman_tone2": {
+ "unicode": "1F646-1F3FC",
+ "unicode_alternates": "",
+ "name": "face with ok gesture tone2",
+ "shortname": ":ok_woman_tone2:",
+ "category": "people",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": [
+ "female",
+ "girl",
+ "human",
+ "pink",
+ "women",
+ "yes",
+ "okay",
+ "accept"
+ ]
+ },
+ "ok_woman_tone3": {
+ "unicode": "1F646-1F3FD",
+ "unicode_alternates": "",
+ "name": "face with ok gesture tone3",
+ "shortname": ":ok_woman_tone3:",
+ "category": "people",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": [
+ "female",
+ "girl",
+ "human",
+ "pink",
+ "women",
+ "yes",
+ "okay",
+ "accept"
+ ]
+ },
+ "ok_woman_tone4": {
+ "unicode": "1F646-1F3FE",
+ "unicode_alternates": "",
+ "name": "face with ok gesture tone4",
+ "shortname": ":ok_woman_tone4:",
+ "category": "people",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": [
+ "female",
+ "girl",
+ "human",
+ "pink",
+ "women",
+ "yes",
+ "okay",
+ "accept"
+ ]
+ },
+ "ok_woman_tone5": {
+ "unicode": "1F646-1F3FF",
+ "unicode_alternates": "",
+ "name": "face with ok gesture tone5",
+ "shortname": ":ok_woman_tone5:",
+ "category": "people",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": [
+ "female",
+ "girl",
+ "human",
+ "pink",
+ "women",
+ "yes",
+ "okay",
+ "accept"
+ ]
+ },
"older_man": {
"unicode": "1F474",
"unicode_alternates": [],
@@ -9286,20 +19511,197 @@
"category": "emoticons",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["human", "male", "men"],
+ "keywords": [
+ "human",
+ "male",
+ "men"
+ ],
"moji": "👴"
},
+ "older_man_tone1": {
+ "unicode": "1F474-1F3FB",
+ "unicode_alternates": "",
+ "name": "older man tone 1",
+ "shortname": ":older_man_tone1:",
+ "category": "people",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": [
+ "male",
+ "men",
+ "grandpa",
+ "grandfather"
+ ]
+ },
+ "older_man_tone2": {
+ "unicode": "1F474-1F3FC",
+ "unicode_alternates": "",
+ "name": "older man tone 2",
+ "shortname": ":older_man_tone2:",
+ "category": "people",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": [
+ "male",
+ "men",
+ "grandpa",
+ "grandfather"
+ ]
+ },
+ "older_man_tone3": {
+ "unicode": "1F474-1F3FD",
+ "unicode_alternates": "",
+ "name": "older man tone 3",
+ "shortname": ":older_man_tone3:",
+ "category": "people",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": [
+ "male",
+ "men",
+ "grandpa",
+ "grandfather"
+ ]
+ },
+ "older_man_tone4": {
+ "unicode": "1F474-1F3FE",
+ "unicode_alternates": "",
+ "name": "older man tone 4",
+ "shortname": ":older_man_tone4:",
+ "category": "people",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": [
+ "male",
+ "men",
+ "grandpa",
+ "grandfather"
+ ]
+ },
+ "older_man_tone5": {
+ "unicode": "1F474-1F3FF",
+ "unicode_alternates": "",
+ "name": "older man tone 5",
+ "shortname": ":older_man_tone5:",
+ "category": "people",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": [
+ "male",
+ "men",
+ "grandpa",
+ "grandfather"
+ ]
+ },
"older_woman": {
"unicode": "1F475",
"unicode_alternates": [],
"name": "older woman",
"shortname": ":older_woman:",
"category": "emoticons",
- "aliases": [":grandma:"],
- "aliases_ascii": [],
- "keywords": ["female", "girl", "women", "grandma", "grandmother"],
+ "aliases": [
+ ":grandma:"
+ ],
+ "aliases_ascii": [],
+ "keywords": [
+ "female",
+ "girl",
+ "women",
+ "grandma",
+ "grandmother"
+ ],
"moji": "👵"
},
+ "older_woman_tone1": {
+ "unicode": "1F475-1F3FB",
+ "unicode_alternates": "",
+ "name": "older woman tone 1",
+ "shortname": ":older_woman_tone1:",
+ "category": "people",
+ "aliases": [
+ ":grandma_tone1:"
+ ],
+ "aliases_ascii": [],
+ "keywords": [
+ "female",
+ "women",
+ "lady",
+ "grandma",
+ "grandmother"
+ ]
+ },
+ "older_woman_tone2": {
+ "unicode": "1F475-1F3FC",
+ "unicode_alternates": "",
+ "name": "older woman tone 2",
+ "shortname": ":older_woman_tone2:",
+ "category": "people",
+ "aliases": [
+ ":grandma_tone2:"
+ ],
+ "aliases_ascii": [],
+ "keywords": [
+ "female",
+ "women",
+ "lady",
+ "grandma",
+ "grandmother"
+ ]
+ },
+ "older_woman_tone3": {
+ "unicode": "1F475-1F3FD",
+ "unicode_alternates": "",
+ "name": "older woman tone 3",
+ "shortname": ":older_woman_tone3:",
+ "category": "people",
+ "aliases": [
+ ":grandma_tone3:"
+ ],
+ "aliases_ascii": [],
+ "keywords": [
+ "female",
+ "women",
+ "lady",
+ "grandma",
+ "grandmother"
+ ]
+ },
+ "older_woman_tone4": {
+ "unicode": "1F475-1F3FE",
+ "unicode_alternates": "",
+ "name": "older woman tone 4",
+ "shortname": ":older_woman_tone4:",
+ "category": "people",
+ "aliases": [
+ ":grandma_tone4:"
+ ],
+ "aliases_ascii": [],
+ "keywords": [
+ "female",
+ "women",
+ "lady",
+ "grandma",
+ "grandmother"
+ ]
+ },
+ "older_woman_tone5": {
+ "unicode": "1F475-1F3FF",
+ "unicode_alternates": "",
+ "name": "older woman tone 5",
+ "shortname": ":older_woman_tone5:",
+ "category": "people",
+ "aliases": [
+ ":grandma_tone5:"
+ ],
+ "aliases_ascii": [],
+ "keywords": [
+ "female",
+ "women",
+ "lady",
+ "grandma",
+ "grandmother"
+ ]
+ },
"om_symbol": {
"unicode": "1F549",
"unicode_alternates": [],
@@ -9308,7 +19710,16 @@
"category": "objects_symbols",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["hinduism", "sound", "spiritual", "icon", "dharmic", "buddhism", "jainism", "meditate"]
+ "keywords": [
+ "hinduism",
+ "sound",
+ "spiritual",
+ "icon",
+ "dharmic",
+ "buddhism",
+ "jainism",
+ "meditate"
+ ]
},
"on": {
"unicode": "1F51B",
@@ -9318,7 +19729,10 @@
"category": "other",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["arrow", "words"],
+ "keywords": [
+ "arrow",
+ "words"
+ ],
"moji": "🔛"
},
"oncoming_automobile": {
@@ -9329,7 +19743,14 @@
"category": "places",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["car", "transportation", "vehicle", "sedan", "car", "automobile"],
+ "keywords": [
+ "car",
+ "transportation",
+ "vehicle",
+ "sedan",
+ "car",
+ "automobile"
+ ],
"moji": "🚘"
},
"oncoming_bus": {
@@ -9340,7 +19761,15 @@
"category": "places",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["transportation", "vehicle", "bus", "school", "city", "transportation", "public"],
+ "keywords": [
+ "transportation",
+ "vehicle",
+ "bus",
+ "school",
+ "city",
+ "transportation",
+ "public"
+ ],
"moji": "🚍"
},
"oncoming_police_car": {
@@ -9351,7 +19780,19 @@
"category": "places",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["enforcement", "law", "vehicle", "police", "car", "emergency", "ticket", "citation", "crime", "help", "officer"],
+ "keywords": [
+ "enforcement",
+ "law",
+ "vehicle",
+ "police",
+ "car",
+ "emergency",
+ "ticket",
+ "citation",
+ "crime",
+ "help",
+ "officer"
+ ],
"moji": "🚔"
},
"oncoming_taxi": {
@@ -9362,19 +19803,35 @@
"category": "places",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["cars", "uber", "vehicle", "taxi", "car", "automobile", "city", "transport", "service"],
+ "keywords": [
+ "cars",
+ "uber",
+ "vehicle",
+ "taxi",
+ "car",
+ "automobile",
+ "city",
+ "transport",
+ "service"
+ ],
"moji": "🚖"
},
"one": {
"moji": "1️⃣",
"unicode": "0031-20E3",
- "unicode_alternates": ["0031-FE0F-20E3"],
+ "unicode_alternates": [
+ "0031-FE0F-20E3"
+ ],
"name": "digit one",
"shortname": ":one:",
"category": "other",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["1", "blue-square", "numbers"]
+ "keywords": [
+ "1",
+ "blue-square",
+ "numbers"
+ ]
},
"open_file_folder": {
"unicode": "1F4C2",
@@ -9384,7 +19841,10 @@
"category": "objects",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["documents", "load"],
+ "keywords": [
+ "documents",
+ "load"
+ ],
"moji": "📂"
},
"open_hands": {
@@ -9395,9 +19855,77 @@
"category": "emoticons",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["butterfly", "fingers"],
+ "keywords": [
+ "butterfly",
+ "fingers"
+ ],
"moji": "👐"
},
+ "open_hands_tone1": {
+ "unicode": "1F450-1F3FB",
+ "unicode_alternates": "",
+ "name": "open hands sign tone 1",
+ "shortname": ":open_hands_tone1:",
+ "category": "people",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": [
+ "butterfly",
+ "fingers"
+ ]
+ },
+ "open_hands_tone2": {
+ "unicode": "1F450-1F3FC",
+ "unicode_alternates": "",
+ "name": "open hands sign tone 2",
+ "shortname": ":open_hands_tone2:",
+ "category": "people",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": [
+ "butterfly",
+ "fingers"
+ ]
+ },
+ "open_hands_tone3": {
+ "unicode": "1F450-1F3FD",
+ "unicode_alternates": "",
+ "name": "open hands sign tone 3",
+ "shortname": ":open_hands_tone3:",
+ "category": "people",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": [
+ "butterfly",
+ "fingers"
+ ]
+ },
+ "open_hands_tone4": {
+ "unicode": "1F450-1F3FE",
+ "unicode_alternates": "",
+ "name": "open hands sign tone 4",
+ "shortname": ":open_hands_tone4:",
+ "category": "people",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": [
+ "butterfly",
+ "fingers"
+ ]
+ },
+ "open_hands_tone5": {
+ "unicode": "1F450-1F3FF",
+ "unicode_alternates": "",
+ "name": "open hands sign tone 5",
+ "shortname": ":open_hands_tone5:",
+ "category": "people",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": [
+ "butterfly",
+ "fingers"
+ ]
+ },
"open_mouth": {
"unicode": "1F62E",
"unicode_alternates": [],
@@ -9405,8 +19933,24 @@
"shortname": ":open_mouth:",
"category": "emoticons",
"aliases": [],
- "aliases_ascii": [":-O", ":O", ":-o", ":o", "O_O", ">:O"],
- "keywords": ["face", "impressed", "mouth", "open", "jaw", "gapping", "surprise", "wow"],
+ "aliases_ascii": [
+ ":-O",
+ ":O",
+ ":-o",
+ ":o",
+ "O_O",
+ ">:O"
+ ],
+ "keywords": [
+ "face",
+ "impressed",
+ "mouth",
+ "open",
+ "jaw",
+ "gapping",
+ "surprise",
+ "wow"
+ ],
"moji": "😮"
},
"ophiuchus": {
@@ -9417,7 +19961,19 @@
"category": "other",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["ophiuchus", "serpent", "snake", "astrology", "greek", "constellation", "stars", "zodiac", "purple-square", "sign", "horoscope"],
+ "keywords": [
+ "ophiuchus",
+ "serpent",
+ "snake",
+ "astrology",
+ "greek",
+ "constellation",
+ "stars",
+ "zodiac",
+ "purple-square",
+ "sign",
+ "horoscope"
+ ],
"moji": "⛎"
},
"optical_disk": {
@@ -9426,9 +19982,17 @@
"name": "optical disc icon",
"shortname": ":optical_disk:",
"category": "objects_symbols",
- "aliases": [":optical_disc_icon:"],
- "aliases_ascii": [],
- "keywords": ["cd", "dvd", "disc", "disk", "technology"]
+ "aliases": [
+ ":optical_disc_icon:"
+ ],
+ "aliases_ascii": [],
+ "keywords": [
+ "cd",
+ "dvd",
+ "disc",
+ "disk",
+ "technology"
+ ]
},
"orange_book": {
"unicode": "1F4D9",
@@ -9438,9 +20002,27 @@
"category": "objects",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["knowledge", "library", "read"],
+ "keywords": [
+ "knowledge",
+ "library",
+ "read"
+ ],
"moji": "📙"
},
+ "orthodox_cross": {
+ "unicode": "2626",
+ "unicode_alternates": "",
+ "name": "orthodox cross",
+ "shortname": ":orthodox_cross:",
+ "category": "symbols",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": [
+ "christian",
+ "religion",
+ "symbol"
+ ]
+ },
"outbox_tray": {
"unicode": "1F4E4",
"unicode_alternates": [],
@@ -9449,7 +20031,10 @@
"category": "objects",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["email", "inbox"],
+ "keywords": [
+ "email",
+ "inbox"
+ ],
"moji": "📤"
},
"ox": {
@@ -9460,7 +20045,11 @@
"category": "nature",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["animal", "beef", "cow"],
+ "keywords": [
+ "animal",
+ "beef",
+ "cow"
+ ],
"moji": "🐂"
},
"package": {
@@ -9471,7 +20060,10 @@
"category": "objects",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["gift", "mail"],
+ "keywords": [
+ "gift",
+ "mail"
+ ],
"moji": "📦"
},
"page": {
@@ -9482,7 +20074,9 @@
"category": "objects_symbols",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["document"]
+ "keywords": [
+ "document"
+ ]
},
"page_facing_up": {
"unicode": "1F4C4",
@@ -9492,7 +20086,9 @@
"category": "objects",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["documents"],
+ "keywords": [
+ "documents"
+ ],
"moji": "📄"
},
"page_with_curl": {
@@ -9503,7 +20099,9 @@
"category": "objects",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["documents"],
+ "keywords": [
+ "documents"
+ ],
"moji": "📃"
},
"pager": {
@@ -9514,7 +20112,10 @@
"category": "objects",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["bbcall", "oldschool"],
+ "keywords": [
+ "bbcall",
+ "oldschool"
+ ],
"moji": "📟"
},
"pages": {
@@ -9525,7 +20126,9 @@
"category": "objects_symbols",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["documents"]
+ "keywords": [
+ "documents"
+ ]
},
"paintbrush": {
"unicode": "1F58C",
@@ -9533,9 +20136,15 @@
"name": "lower left paintbrush",
"shortname": ":paintbrush:",
"category": "objects_symbols",
- "aliases": [":lower_left_paintbrush:"],
+ "aliases": [
+ ":lower_left_paintbrush:"
+ ],
"aliases_ascii": [],
- "keywords": ["brush", "art", "painting"]
+ "keywords": [
+ "brush",
+ "art",
+ "painting"
+ ]
},
"palm_tree": {
"unicode": "1F334",
@@ -9545,7 +20154,17 @@
"category": "nature",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["nature", "plant", "vegetable", "palm", "tree", "coconuts", "fronds", "warm", "tropical"],
+ "keywords": [
+ "nature",
+ "plant",
+ "vegetable",
+ "palm",
+ "tree",
+ "coconuts",
+ "fronds",
+ "warm",
+ "tropical"
+ ],
"moji": "🌴"
},
"panda_face": {
@@ -9556,7 +20175,22 @@
"category": "nature",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["animal", "nature", "panda", "bear", "face", "cub", "cute", "endearment", "friendship", "love", "bamboo", "china", "black", "white"],
+ "keywords": [
+ "animal",
+ "nature",
+ "panda",
+ "bear",
+ "face",
+ "cub",
+ "cute",
+ "endearment",
+ "friendship",
+ "love",
+ "bamboo",
+ "china",
+ "black",
+ "white"
+ ],
"moji": "🐼"
},
"paperclip": {
@@ -9567,7 +20201,10 @@
"category": "objects",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["documents", "stationery"],
+ "keywords": [
+ "documents",
+ "stationery"
+ ],
"moji": "📎"
},
"paperclips": {
@@ -9576,9 +20213,14 @@
"name": "linked paperclips",
"shortname": ":paperclips:",
"category": "objects_symbols",
- "aliases": [":linked_paperclips:"],
+ "aliases": [
+ ":linked_paperclips:"
+ ],
"aliases_ascii": [],
- "keywords": ["documents", "stationery"]
+ "keywords": [
+ "documents",
+ "stationery"
+ ]
},
"park": {
"unicode": "1F3DE",
@@ -9586,41 +20228,77 @@
"name": "national park",
"shortname": ":park:",
"category": "travel_places",
- "aliases": [":national_park:"],
- "aliases_ascii": [],
- "keywords": ["woods", "nature", "wildlife", "forest", "wilderness", "national"]
+ "aliases": [
+ ":national_park:"
+ ],
+ "aliases_ascii": [],
+ "keywords": [
+ "woods",
+ "nature",
+ "wildlife",
+ "forest",
+ "wilderness",
+ "national"
+ ]
},
"parking": {
"unicode": "1F17F",
- "unicode_alternates": ["1F17F-FE0F"],
+ "unicode_alternates": [
+ "1F17F-FE0F"
+ ],
"name": "negative squared latin capital letter p",
"shortname": ":parking:",
"category": "other",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["alphabet", "blue-square", "cars", "letter"],
+ "keywords": [
+ "alphabet",
+ "blue-square",
+ "cars",
+ "letter"
+ ],
"moji": "🅿"
},
"part_alternation_mark": {
"unicode": "303D",
- "unicode_alternates": ["303D-FE0F"],
+ "unicode_alternates": [
+ "303D-FE0F"
+ ],
"name": "part alternation mark",
"shortname": ":part_alternation_mark:",
"category": "other",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["graph", "sing", "song", "vocal", "music", "karaoke", "cue", "letter", "m", "japanese"],
+ "keywords": [
+ "graph",
+ "sing",
+ "song",
+ "vocal",
+ "music",
+ "karaoke",
+ "cue",
+ "letter",
+ "m",
+ "japanese"
+ ],
"moji": "〽"
},
"partly_sunny": {
"unicode": "26C5",
- "unicode_alternates": ["26C5-FE0F"],
+ "unicode_alternates": [
+ "26C5-FE0F"
+ ],
"name": "sun behind cloud",
"shortname": ":partly_sunny:",
"category": "nature",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["cloud", "morning", "nature", "weather"],
+ "keywords": [
+ "cloud",
+ "morning",
+ "nature",
+ "weather"
+ ],
"moji": "⛅"
},
"passport_control": {
@@ -9631,9 +20309,48 @@
"category": "other",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["blue-square", "custom", "passport", "official", "travel", "control", "foreign", "identification"],
+ "keywords": [
+ "blue-square",
+ "custom",
+ "passport",
+ "official",
+ "travel",
+ "control",
+ "foreign",
+ "identification"
+ ],
"moji": "🛂"
},
+ "pause_button": {
+ "unicode": "23F8",
+ "unicode_alternates": "",
+ "name": "double vertical bar",
+ "shortname": ":pause_button:",
+ "category": "symbols",
+ "aliases": [
+ ":double_vertical_bar:"
+ ],
+ "aliases_ascii": [],
+ "keywords": [
+ "pause",
+ "sound",
+ "symbol"
+ ]
+ },
+ "peace": {
+ "unicode": "262E",
+ "unicode_alternates": "",
+ "name": "peace symbol",
+ "shortname": ":peace:",
+ "category": "symbols",
+ "aliases": [
+ ":peace_symbol:"
+ ],
+ "aliases_ascii": [],
+ "keywords": [
+ "sign"
+ ]
+ },
"peach": {
"unicode": "1F351",
"unicode_alternates": [],
@@ -9642,7 +20359,15 @@
"category": "objects",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["food", "fruit", "nature", "peach", "fruit", "juicy", "pit"],
+ "keywords": [
+ "food",
+ "fruit",
+ "nature",
+ "peach",
+ "fruit",
+ "juicy",
+ "pit"
+ ],
"moji": "🍑"
},
"pear": {
@@ -9653,7 +20378,13 @@
"category": "objects",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["fruit", "nature", "pear", "fruit", "shape"],
+ "keywords": [
+ "fruit",
+ "nature",
+ "pear",
+ "fruit",
+ "shape"
+ ],
"moji": "🍐"
},
"pen_ballpoint": {
@@ -9662,9 +20393,15 @@
"name": "lower left ballpoint pen",
"shortname": ":pen_ballpoint:",
"category": "objects_symbols",
- "aliases": [":lower_left_ballpoint_pen:"],
+ "aliases": [
+ ":lower_left_ballpoint_pen:"
+ ],
"aliases_ascii": [],
- "keywords": ["write", "bic", "ink"]
+ "keywords": [
+ "write",
+ "bic",
+ "ink"
+ ]
},
"pen_fountain": {
"unicode": "1F58B",
@@ -9672,9 +20409,15 @@
"name": "lower left fountain pen",
"shortname": ":pen_fountain:",
"category": "objects_symbols",
- "aliases": [":lower_left_fountain_pen:"],
+ "aliases": [
+ ":lower_left_fountain_pen:"
+ ],
"aliases_ascii": [],
- "keywords": ["write", "calligraphy", "ink"]
+ "keywords": [
+ "write",
+ "calligraphy",
+ "ink"
+ ]
},
"pencil": {
"unicode": "1F4DD",
@@ -9682,20 +20425,33 @@
"name": "memo",
"shortname": ":pencil:",
"category": "objects",
- "aliases": [":memo:"],
- "aliases_ascii": [],
- "keywords": ["documents", "paper", "station", "write"],
+ "aliases": [
+ ":memo:"
+ ],
+ "aliases_ascii": [],
+ "keywords": [
+ "documents",
+ "paper",
+ "station",
+ "write"
+ ],
"moji": "📝"
},
"pencil2": {
"unicode": "270F",
- "unicode_alternates": ["270F-FE0F"],
+ "unicode_alternates": [
+ "270F-FE0F"
+ ],
"name": "pencil",
"shortname": ":pencil2:",
"category": "objects",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["paper", "stationery", "write"],
+ "keywords": [
+ "paper",
+ "stationery",
+ "write"
+ ],
"moji": "✏"
},
"pencil3": {
@@ -9704,9 +20460,15 @@
"name": "lower left pencil",
"shortname": ":pencil3:",
"category": "objects_symbols",
- "aliases": [":lower_left_pencil:"],
+ "aliases": [
+ ":lower_left_pencil:"
+ ],
"aliases_ascii": [],
- "keywords": ["paper", "stationery", "write"]
+ "keywords": [
+ "paper",
+ "stationery",
+ "write"
+ ]
},
"penguin": {
"unicode": "1F427",
@@ -9716,7 +20478,10 @@
"category": "nature",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["animal", "nature"],
+ "keywords": [
+ "animal",
+ "nature"
+ ],
"moji": "🐧"
},
"pennant_black": {
@@ -9725,9 +20490,14 @@
"name": "black pennant",
"shortname": ":pennant_black:",
"category": "objects_symbols",
- "aliases": [":black_pennant:"],
+ "aliases": [
+ ":black_pennant:"
+ ],
"aliases_ascii": [],
- "keywords": ["flag", "athletics"]
+ "keywords": [
+ "flag",
+ "athletics"
+ ]
},
"pennant_white": {
"unicode": "1F3F1",
@@ -9735,9 +20505,14 @@
"name": "white pennant",
"shortname": ":pennant_white:",
"category": "objects_symbols",
- "aliases": [":white_pennant:"],
+ "aliases": [
+ ":white_pennant:"
+ ],
"aliases_ascii": [],
- "keywords": ["flag", "athletics"]
+ "keywords": [
+ "flag",
+ "athletics"
+ ]
},
"pensive": {
"unicode": "1F614",
@@ -9747,7 +20522,18 @@
"category": "emoticons",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["face", "okay", "sad", "pensive", "thoughtful", "think", "reflective", "wistful", "meditate", "serious"],
+ "keywords": [
+ "face",
+ "okay",
+ "sad",
+ "pensive",
+ "thoughtful",
+ "think",
+ "reflective",
+ "wistful",
+ "meditate",
+ "serious"
+ ],
"moji": "😔"
},
"performing_arts": {
@@ -9758,7 +20544,19 @@
"category": "places",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["acting", "drama", "theater", "performing", "arts", "performance", "entertainment", "acting", "story", "mask", "masks"],
+ "keywords": [
+ "acting",
+ "drama",
+ "theater",
+ "performing",
+ "arts",
+ "performance",
+ "entertainment",
+ "acting",
+ "story",
+ "mask",
+ "masks"
+ ],
"moji": "🎭"
},
"persevere": {
@@ -9768,8 +20566,17 @@
"shortname": ":persevere:",
"category": "emoticons",
"aliases": [],
- "aliases_ascii": [">.<"],
- "keywords": ["endure", "persevere", "face", "no", "sick", "upset"],
+ "aliases_ascii": [
+ ">.<"
+ ],
+ "keywords": [
+ "endure",
+ "persevere",
+ "face",
+ "no",
+ "sick",
+ "upset"
+ ],
"moji": "😣"
},
"person_frowning": {
@@ -9780,9 +20587,107 @@
"category": "emoticons",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["female", "girl", "woman", "dejected", "rejected", "sad", "frown"],
+ "keywords": [
+ "female",
+ "girl",
+ "woman",
+ "dejected",
+ "rejected",
+ "sad",
+ "frown"
+ ],
"moji": "🙍"
},
+ "person_frowning_tone1": {
+ "unicode": "1F64D-1F3FB",
+ "unicode_alternates": "",
+ "name": "person frowning tone 1",
+ "shortname": ":person_frowning_tone1:",
+ "category": "people",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": [
+ "female",
+ "girl",
+ "woman",
+ "dejected",
+ "rejected",
+ "sad",
+ "frown"
+ ]
+ },
+ "person_frowning_tone2": {
+ "unicode": "1F64D-1F3FC",
+ "unicode_alternates": "",
+ "name": "person frowning tone 2",
+ "shortname": ":person_frowning_tone2:",
+ "category": "people",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": [
+ "female",
+ "girl",
+ "woman",
+ "dejected",
+ "rejected",
+ "sad",
+ "frown"
+ ]
+ },
+ "person_frowning_tone3": {
+ "unicode": "1F64D-1F3FD",
+ "unicode_alternates": "",
+ "name": "person frowning tone 3",
+ "shortname": ":person_frowning_tone3:",
+ "category": "people",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": [
+ "female",
+ "girl",
+ "woman",
+ "dejected",
+ "rejected",
+ "sad",
+ "frown"
+ ]
+ },
+ "person_frowning_tone4": {
+ "unicode": "1F64D-1F3FE",
+ "unicode_alternates": "",
+ "name": "person frowning tone 4",
+ "shortname": ":person_frowning_tone4:",
+ "category": "people",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": [
+ "female",
+ "girl",
+ "woman",
+ "dejected",
+ "rejected",
+ "sad",
+ "frown"
+ ]
+ },
+ "person_frowning_tone5": {
+ "unicode": "1F64D-1F3FF",
+ "unicode_alternates": "",
+ "name": "person frowning tone 5",
+ "shortname": ":person_frowning_tone5:",
+ "category": "people",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": [
+ "female",
+ "girl",
+ "woman",
+ "dejected",
+ "rejected",
+ "sad",
+ "frown"
+ ]
+ },
"person_with_blond_hair": {
"unicode": "1F471",
"unicode_alternates": [],
@@ -9791,9 +20696,107 @@
"category": "emoticons",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["male", "man", "blonde", "young", "western", "westerner", "occidental"],
+ "keywords": [
+ "male",
+ "man",
+ "blonde",
+ "young",
+ "western",
+ "westerner",
+ "occidental"
+ ],
"moji": "👱"
},
+ "person_with_blond_hair_tone1": {
+ "unicode": "1F471-1F3FB",
+ "unicode_alternates": "",
+ "name": "person with blond hair tone 1",
+ "shortname": ":person_with_blond_hair_tone1:",
+ "category": "people",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": [
+ "male",
+ "man",
+ "blonde",
+ "young",
+ "western",
+ "westerner",
+ "occidental"
+ ]
+ },
+ "person_with_blond_hair_tone2": {
+ "unicode": "1F471-1F3FC",
+ "unicode_alternates": "",
+ "name": "person with blond hair tone 2",
+ "shortname": ":person_with_blond_hair_tone2:",
+ "category": "people",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": [
+ "male",
+ "man",
+ "blonde",
+ "young",
+ "western",
+ "westerner",
+ "occidental"
+ ]
+ },
+ "person_with_blond_hair_tone3": {
+ "unicode": "1F471-1F3FD",
+ "unicode_alternates": "",
+ "name": "person with blond hair tone 3",
+ "shortname": ":person_with_blond_hair_tone3:",
+ "category": "people",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": [
+ "male",
+ "man",
+ "blonde",
+ "young",
+ "western",
+ "westerner",
+ "occidental"
+ ]
+ },
+ "person_with_blond_hair_tone4": {
+ "unicode": "1F471-1F3FE",
+ "unicode_alternates": "",
+ "name": "person with blond hair tone 4",
+ "shortname": ":person_with_blond_hair_tone4:",
+ "category": "people",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": [
+ "male",
+ "man",
+ "blonde",
+ "young",
+ "western",
+ "westerner",
+ "occidental"
+ ]
+ },
+ "person_with_blond_hair_tone5": {
+ "unicode": "1F471-1F3FF",
+ "unicode_alternates": "",
+ "name": "person with blond hair tone 5",
+ "shortname": ":person_with_blond_hair_tone5:",
+ "category": "people",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": [
+ "male",
+ "man",
+ "blonde",
+ "young",
+ "western",
+ "westerner",
+ "occidental"
+ ]
+ },
"person_with_pouting_face": {
"unicode": "1F64E",
"unicode_alternates": [],
@@ -9802,9 +20805,121 @@
"category": "emoticons",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["female", "girl", "woman", "pout", "sexy", "cute", "annoyed"],
+ "keywords": [
+ "female",
+ "girl",
+ "woman",
+ "pout",
+ "sexy",
+ "cute",
+ "annoyed"
+ ],
"moji": "🙎"
},
+ "person_with_pouting_face_tone1": {
+ "unicode": "1F64E-1F3FB",
+ "unicode_alternates": "",
+ "name": "person with pouting face tone1",
+ "shortname": ":person_with_pouting_face_tone1:",
+ "category": "people",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": [
+ "female",
+ "girl",
+ "woman",
+ "pout",
+ "sexy",
+ "cute",
+ "annoyed"
+ ]
+ },
+ "person_with_pouting_face_tone2": {
+ "unicode": "1F64E-1F3FC",
+ "unicode_alternates": "",
+ "name": "person with pouting face tone2",
+ "shortname": ":person_with_pouting_face_tone2:",
+ "category": "people",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": [
+ "female",
+ "girl",
+ "woman",
+ "pout",
+ "sexy",
+ "cute",
+ "annoyed"
+ ]
+ },
+ "person_with_pouting_face_tone3": {
+ "unicode": "1F64E-1F3FD",
+ "unicode_alternates": "",
+ "name": "person with pouting face tone3",
+ "shortname": ":person_with_pouting_face_tone3:",
+ "category": "people",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": [
+ "female",
+ "girl",
+ "woman",
+ "pout",
+ "sexy",
+ "cute",
+ "annoyed"
+ ]
+ },
+ "person_with_pouting_face_tone4": {
+ "unicode": "1F64E-1F3FE",
+ "unicode_alternates": "",
+ "name": "person with pouting face tone4",
+ "shortname": ":person_with_pouting_face_tone4:",
+ "category": "people",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": [
+ "female",
+ "girl",
+ "woman",
+ "pout",
+ "sexy",
+ "cute",
+ "annoyed"
+ ]
+ },
+ "person_with_pouting_face_tone5": {
+ "unicode": "1F64E-1F3FF",
+ "unicode_alternates": "",
+ "name": "person with pouting face tone5",
+ "shortname": ":person_with_pouting_face_tone5:",
+ "category": "people",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": [
+ "female",
+ "girl",
+ "woman",
+ "pout",
+ "sexy",
+ "cute",
+ "annoyed"
+ ]
+ },
+ "pick": {
+ "unicode": "26CF",
+ "unicode_alternates": "",
+ "name": "pick",
+ "shortname": ":pick:",
+ "category": "objects",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": [
+ "mining",
+ "object",
+ "tool"
+ ]
+ },
"pig": {
"unicode": "1F437",
"unicode_alternates": [],
@@ -9813,7 +20928,10 @@
"category": "nature",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["animal", "oink"],
+ "keywords": [
+ "animal",
+ "oink"
+ ],
"moji": "🐷"
},
"pig2": {
@@ -9824,7 +20942,21 @@
"category": "nature",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["animal", "nature", "pig", "piggy", "pork", "ham", "hog", "bacon", "oink", "slop", "livestock", "greed", "greedy"],
+ "keywords": [
+ "animal",
+ "nature",
+ "pig",
+ "piggy",
+ "pork",
+ "ham",
+ "hog",
+ "bacon",
+ "oink",
+ "slop",
+ "livestock",
+ "greed",
+ "greedy"
+ ],
"moji": "🐖"
},
"pig_nose": {
@@ -9835,7 +20967,20 @@
"category": "nature",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["animal", "oink", "pig", "nose", "snout", "food", "eat", "cute", "oink", "pink", "smell", "truffle"],
+ "keywords": [
+ "animal",
+ "oink",
+ "pig",
+ "nose",
+ "snout",
+ "food",
+ "eat",
+ "cute",
+ "oink",
+ "pink",
+ "smell",
+ "truffle"
+ ],
"moji": "🐽"
},
"pill": {
@@ -9846,7 +20991,10 @@
"category": "objects",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["health", "medicine"],
+ "keywords": [
+ "health",
+ "medicine"
+ ],
"moji": "💊"
},
"pineapple": {
@@ -9857,28 +21005,68 @@
"category": "objects",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["food", "fruit", "nature", "pineapple", "pina", "tropical", "flower"],
+ "keywords": [
+ "food",
+ "fruit",
+ "nature",
+ "pineapple",
+ "pina",
+ "tropical",
+ "flower"
+ ],
"moji": "🍍"
},
+ "ping_pong": {
+ "unicode": "1F3D3",
+ "unicode_alternates": "",
+ "name": "table tennis paddle and ball",
+ "shortname": ":ping_pong:",
+ "category": "activity",
+ "aliases": [
+ ":table_tennis:"
+ ],
+ "aliases_ascii": [],
+ "keywords": []
+ },
"piracy": {
"unicode": "1F572",
"unicode_alternates": [],
"name": "no piracy",
"shortname": ":piracy:",
"category": "objects_symbols",
- "aliases": [":no_piracy:"],
+ "aliases": [
+ ":no_piracy:"
+ ],
"aliases_ascii": [],
- "keywords": ["theft", "rule"]
+ "keywords": [
+ "theft",
+ "rule"
+ ]
},
"pisces": {
"unicode": "2653",
- "unicode_alternates": ["2653-FE0F"],
+ "unicode_alternates": [
+ "2653-FE0F"
+ ],
"name": "pisces",
"shortname": ":pisces:",
"category": "other",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["pisces", "fish", "astrology", "greek", "constellation", "stars", "zodiac", "sign", "purple-square", "sign", "zodiac", "horoscope"],
+ "keywords": [
+ "pisces",
+ "fish",
+ "astrology",
+ "greek",
+ "constellation",
+ "stars",
+ "zodiac",
+ "sign",
+ "purple-square",
+ "sign",
+ "zodiac",
+ "horoscope"
+ ],
"moji": "♓"
},
"pizza": {
@@ -9889,9 +21077,48 @@
"category": "objects",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["food", "party", "pizza", "pie", "new york", "italian", "italy", "slice", "peperoni"],
+ "keywords": [
+ "food",
+ "party",
+ "pizza",
+ "pie",
+ "new york",
+ "italian",
+ "italy",
+ "slice",
+ "peperoni"
+ ],
"moji": "🍕"
},
+ "place_of_worship": {
+ "unicode": "1F6D0",
+ "unicode_alternates": "",
+ "name": "place of worship",
+ "shortname": ":place_of_worship:",
+ "category": "symbols",
+ "aliases": [
+ ":worship_symbol:"
+ ],
+ "aliases_ascii": [],
+ "keywords": []
+ },
+ "play_pause": {
+ "unicode": "23EF",
+ "unicode_alternates": "",
+ "name": "black right-pointing double triangle with double vertical bar",
+ "shortname": ":play_pause:",
+ "category": "symbols",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": [
+ "arrow",
+ "pause",
+ "play",
+ "right",
+ "sound",
+ "symbol"
+ ]
+ },
"point_down": {
"unicode": "1F447",
"unicode_alternates": [],
@@ -9900,9 +21127,83 @@
"category": "emoticons",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["direction", "fingers", "hand"],
+ "keywords": [
+ "direction",
+ "fingers",
+ "hand"
+ ],
"moji": "👇"
},
+ "point_down_tone1": {
+ "unicode": "1F447-1F3FB",
+ "unicode_alternates": "",
+ "name": "white down pointing backhand index tone 1",
+ "shortname": ":point_down_tone1:",
+ "category": "people",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": [
+ "direction",
+ "finger",
+ "hand"
+ ]
+ },
+ "point_down_tone2": {
+ "unicode": "1F447-1F3FC",
+ "unicode_alternates": "",
+ "name": "white down pointing backhand index tone 2",
+ "shortname": ":point_down_tone2:",
+ "category": "people",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": [
+ "direction",
+ "finger",
+ "hand"
+ ]
+ },
+ "point_down_tone3": {
+ "unicode": "1F447-1F3FD",
+ "unicode_alternates": "",
+ "name": "white down pointing backhand index tone 3",
+ "shortname": ":point_down_tone3:",
+ "category": "people",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": [
+ "direction",
+ "finger",
+ "hand"
+ ]
+ },
+ "point_down_tone4": {
+ "unicode": "1F447-1F3FE",
+ "unicode_alternates": "",
+ "name": "white down pointing backhand index tone 4",
+ "shortname": ":point_down_tone4:",
+ "category": "people",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": [
+ "direction",
+ "finger",
+ "hand"
+ ]
+ },
+ "point_down_tone5": {
+ "unicode": "1F447-1F3FF",
+ "unicode_alternates": "",
+ "name": "white down pointing backhand index tone 5",
+ "shortname": ":point_down_tone5:",
+ "category": "people",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": [
+ "direction",
+ "finger",
+ "hand"
+ ]
+ },
"point_left": {
"unicode": "1F448",
"unicode_alternates": [],
@@ -9911,9 +21212,83 @@
"category": "emoticons",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["direction", "fingers", "hand"],
+ "keywords": [
+ "direction",
+ "fingers",
+ "hand"
+ ],
"moji": "👈"
},
+ "point_left_tone1": {
+ "unicode": "1F448-1F3FB",
+ "unicode_alternates": "",
+ "name": "white left pointing backhand index tone 1",
+ "shortname": ":point_left_tone1:",
+ "category": "people",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": [
+ "direction",
+ "finger",
+ "hand"
+ ]
+ },
+ "point_left_tone2": {
+ "unicode": "1F448-1F3FC",
+ "unicode_alternates": "",
+ "name": "white left pointing backhand index tone 2",
+ "shortname": ":point_left_tone2:",
+ "category": "people",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": [
+ "direction",
+ "finger",
+ "hand"
+ ]
+ },
+ "point_left_tone3": {
+ "unicode": "1F448-1F3FD",
+ "unicode_alternates": "",
+ "name": "white left pointing backhand index tone 3",
+ "shortname": ":point_left_tone3:",
+ "category": "people",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": [
+ "direction",
+ "finger",
+ "hand"
+ ]
+ },
+ "point_left_tone4": {
+ "unicode": "1F448-1F3FE",
+ "unicode_alternates": "",
+ "name": "white left pointing backhand index tone 4",
+ "shortname": ":point_left_tone4:",
+ "category": "people",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": [
+ "direction",
+ "finger",
+ "hand"
+ ]
+ },
+ "point_left_tone5": {
+ "unicode": "1F448-1F3FF",
+ "unicode_alternates": "",
+ "name": "white left pointing backhand index tone 5",
+ "shortname": ":point_left_tone5:",
+ "category": "people",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": [
+ "direction",
+ "finger",
+ "hand"
+ ]
+ },
"point_right": {
"unicode": "1F449",
"unicode_alternates": [],
@@ -9922,18 +21297,98 @@
"category": "emoticons",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["direction", "fingers", "hand"],
+ "keywords": [
+ "direction",
+ "fingers",
+ "hand"
+ ],
"moji": "👉"
},
+ "point_right_tone1": {
+ "unicode": "1F449-1F3FB",
+ "unicode_alternates": "",
+ "name": "white right pointing backhand index tone 1",
+ "shortname": ":point_right_tone1:",
+ "category": "people",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": [
+ "direction",
+ "finger",
+ "hand"
+ ]
+ },
+ "point_right_tone2": {
+ "unicode": "1F449-1F3FC",
+ "unicode_alternates": "",
+ "name": "white right pointing backhand index tone 2",
+ "shortname": ":point_right_tone2:",
+ "category": "people",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": [
+ "direction",
+ "finger",
+ "hand"
+ ]
+ },
+ "point_right_tone3": {
+ "unicode": "1F449-1F3FD",
+ "unicode_alternates": "",
+ "name": "white right pointing backhand index tone 3",
+ "shortname": ":point_right_tone3:",
+ "category": "people",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": [
+ "direction",
+ "finger",
+ "hand"
+ ]
+ },
+ "point_right_tone4": {
+ "unicode": "1F449-1F3FE",
+ "unicode_alternates": "",
+ "name": "white right pointing backhand index tone 4",
+ "shortname": ":point_right_tone4:",
+ "category": "people",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": [
+ "direction",
+ "finger",
+ "hand"
+ ]
+ },
+ "point_right_tone5": {
+ "unicode": "1F449-1F3FF",
+ "unicode_alternates": "",
+ "name": "white right pointing backhand index tone 5",
+ "shortname": ":point_right_tone5:",
+ "category": "people",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": [
+ "direction",
+ "finger",
+ "hand"
+ ]
+ },
"point_up": {
"unicode": "261D",
- "unicode_alternates": ["261D-FE0F"],
+ "unicode_alternates": [
+ "261D-FE0F"
+ ],
"name": "white up pointing index",
"shortname": ":point_up:",
"category": "emoticons",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["direction", "fingers", "hand"],
+ "keywords": [
+ "direction",
+ "fingers",
+ "hand"
+ ],
"moji": "☝"
},
"point_up_2": {
@@ -9944,9 +21399,163 @@
"category": "emoticons",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["direction", "fingers", "hand"],
+ "keywords": [
+ "direction",
+ "fingers",
+ "hand"
+ ],
"moji": "👆"
},
+ "point_up_2_tone1": {
+ "unicode": "1F446-1F3FB",
+ "unicode_alternates": "",
+ "name": "white up pointing backhand index tone 1",
+ "shortname": ":point_up_2_tone1:",
+ "category": "people",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": [
+ "direction",
+ "finger",
+ "hand",
+ "one"
+ ]
+ },
+ "point_up_2_tone2": {
+ "unicode": "1F446-1F3FC",
+ "unicode_alternates": "",
+ "name": "white up pointing backhand index tone 2",
+ "shortname": ":point_up_2_tone2:",
+ "category": "people",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": [
+ "direction",
+ "finger",
+ "hand",
+ "one"
+ ]
+ },
+ "point_up_2_tone3": {
+ "unicode": "1F446-1F3FD",
+ "unicode_alternates": "",
+ "name": "white up pointing backhand index tone 3",
+ "shortname": ":point_up_2_tone3:",
+ "category": "people",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": [
+ "direction",
+ "finger",
+ "hand",
+ "one"
+ ]
+ },
+ "point_up_2_tone4": {
+ "unicode": "1F446-1F3FE",
+ "unicode_alternates": "",
+ "name": "white up pointing backhand index tone 4",
+ "shortname": ":point_up_2_tone4:",
+ "category": "people",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": [
+ "direction",
+ "finger",
+ "hand",
+ "one"
+ ]
+ },
+ "point_up_2_tone5": {
+ "unicode": "1F446-1F3FF",
+ "unicode_alternates": "",
+ "name": "white up pointing backhand index tone 5",
+ "shortname": ":point_up_2_tone5:",
+ "category": "people",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": [
+ "direction",
+ "finger",
+ "hand",
+ "one"
+ ]
+ },
+ "point_up_tone1": {
+ "unicode": "261D-1F3FB",
+ "unicode_alternates": "",
+ "name": "white up pointing index tone 1",
+ "shortname": ":point_up_tone1:",
+ "category": "people",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": [
+ "direction",
+ "finger",
+ "hand",
+ "one"
+ ]
+ },
+ "point_up_tone2": {
+ "unicode": "261D-1F3FC",
+ "unicode_alternates": "",
+ "name": "white up pointing index tone 2",
+ "shortname": ":point_up_tone2:",
+ "category": "people",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": [
+ "direction",
+ "finger",
+ "hand",
+ "one"
+ ]
+ },
+ "point_up_tone3": {
+ "unicode": "261D-1F3FD",
+ "unicode_alternates": "",
+ "name": "white up pointing index tone 3",
+ "shortname": ":point_up_tone3:",
+ "category": "people",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": [
+ "direction",
+ "finger",
+ "hand",
+ "one"
+ ]
+ },
+ "point_up_tone4": {
+ "unicode": "261D-1F3FE",
+ "unicode_alternates": "",
+ "name": "white up pointing index tone 4",
+ "shortname": ":point_up_tone4:",
+ "category": "people",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": [
+ "direction",
+ "finger",
+ "hand",
+ "one"
+ ]
+ },
+ "point_up_tone5": {
+ "unicode": "261D-1F3FF",
+ "unicode_alternates": "",
+ "name": "white up pointing index tone 5",
+ "shortname": ":point_up_tone5:",
+ "category": "people",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": [
+ "direction",
+ "finger",
+ "hand",
+ "one"
+ ]
+ },
"police_car": {
"unicode": "1F693",
"unicode_alternates": [],
@@ -9955,7 +21564,21 @@
"category": "places",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["cars", "enforcement", "law", "transportation", "vehicle", "police", "car", "emergency", "ticket", "citation", "crime", "help", "officer"],
+ "keywords": [
+ "cars",
+ "enforcement",
+ "law",
+ "transportation",
+ "vehicle",
+ "police",
+ "car",
+ "emergency",
+ "ticket",
+ "citation",
+ "crime",
+ "help",
+ "officer"
+ ],
"moji": "🚓"
},
"poodle": {
@@ -9966,7 +21589,18 @@
"category": "nature",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["101", "animal", "dog", "nature", "poodle", "dog", "clip", "showy", "sophisticated", "vain"],
+ "keywords": [
+ "101",
+ "animal",
+ "dog",
+ "nature",
+ "poodle",
+ "dog",
+ "clip",
+ "showy",
+ "sophisticated",
+ "vain"
+ ],
"moji": "🐩"
},
"poop": {
@@ -9975,11 +21609,31 @@
"name": "pile of poo",
"shortname": ":poop:",
"category": "emoticons",
- "aliases": [":shit:", ":hankey:", ":poo:"],
- "aliases_ascii": [],
- "keywords": ["poop", "shit", "shitface", "turd", "poo"],
+ "aliases": [
+ ":shit:",
+ ":hankey:",
+ ":poo:"
+ ],
+ "aliases_ascii": [],
+ "keywords": [
+ "poop",
+ "shit",
+ "shitface",
+ "turd",
+ "poo"
+ ],
"moji": "💩"
},
+ "popcorn": {
+ "unicode": "1F37F",
+ "unicode_alternates": "",
+ "name": "popcorn",
+ "shortname": ":popcorn:",
+ "category": "foods",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": []
+ },
"post_office": {
"unicode": "1F3E3",
"unicode_alternates": [],
@@ -9988,7 +21642,11 @@
"category": "places",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["building", "communication", "email"],
+ "keywords": [
+ "building",
+ "communication",
+ "email"
+ ],
"moji": "🏣"
},
"postal_horn": {
@@ -9999,7 +21657,10 @@
"category": "objects",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["instrument", "music"],
+ "keywords": [
+ "instrument",
+ "music"
+ ],
"moji": "📯"
},
"postbox": {
@@ -10010,7 +21671,11 @@
"category": "objects",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["email", "envelope", "letter"],
+ "keywords": [
+ "email",
+ "envelope",
+ "letter"
+ ],
"moji": "📮"
},
"potable_water": {
@@ -10021,7 +21686,21 @@
"category": "other",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["blue-square", "cleaning", "faucet", "liquid", "restroom", "potable", "water", "drinkable", "pure", "clear", "clean", "aqua", "h20"],
+ "keywords": [
+ "blue-square",
+ "cleaning",
+ "faucet",
+ "liquid",
+ "restroom",
+ "potable",
+ "water",
+ "drinkable",
+ "pure",
+ "clear",
+ "clean",
+ "aqua",
+ "h20"
+ ],
"moji": "🚰"
},
"pouch": {
@@ -10032,7 +21711,16 @@
"category": "emoticons",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["accessories", "bag", "pouch", "bag", "cosmetic", "packing", "grandma", "makeup"],
+ "keywords": [
+ "accessories",
+ "bag",
+ "pouch",
+ "bag",
+ "cosmetic",
+ "packing",
+ "grandma",
+ "makeup"
+ ],
"moji": "👝"
},
"poultry_leg": {
@@ -10043,7 +21731,14 @@
"category": "objects",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["food", "meat", "poultry", "leg", "chicken", "fried"],
+ "keywords": [
+ "food",
+ "meat",
+ "poultry",
+ "leg",
+ "chicken",
+ "fried"
+ ],
"moji": "🍗"
},
"pound": {
@@ -10054,7 +21749,24 @@
"category": "objects",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["bills", "british", "currency", "england", "money", "sterling", "uk", "pound", "britain", "british", "banknote", "money", "currency", "paper", "cash", "bills"],
+ "keywords": [
+ "bills",
+ "british",
+ "currency",
+ "england",
+ "money",
+ "sterling",
+ "uk",
+ "pound",
+ "britain",
+ "british",
+ "banknote",
+ "money",
+ "currency",
+ "paper",
+ "cash",
+ "bills"
+ ],
"moji": "💷"
},
"pouting_cat": {
@@ -10065,7 +21777,15 @@
"category": "emoticons",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["animal", "cats", "pout", "annoyed", "miffed", "glower", "frown"],
+ "keywords": [
+ "animal",
+ "cats",
+ "pout",
+ "annoyed",
+ "miffed",
+ "glower",
+ "frown"
+ ],
"moji": "😾"
},
"pray": {
@@ -10076,9 +21796,136 @@
"category": "emoticons",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["highfive", "hope", "namaste", "please", "wish", "pray", "high five", "hands", "sorrow", "regret", "sorry"],
+ "keywords": [
+ "highfive",
+ "hope",
+ "namaste",
+ "please",
+ "wish",
+ "pray",
+ "high five",
+ "hands",
+ "sorrow",
+ "regret",
+ "sorry"
+ ],
"moji": "🙏"
},
+ "pray_tone1": {
+ "unicode": "1F64F-1F3FB",
+ "unicode_alternates": "",
+ "name": "person with folded hands tone 1",
+ "shortname": ":pray_tone1:",
+ "category": "people",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": [
+ "highfive",
+ "hope",
+ "namaste",
+ "please",
+ "wish",
+ "pray",
+ "high five",
+ "sorrow",
+ "regret",
+ "sorry"
+ ]
+ },
+ "pray_tone2": {
+ "unicode": "1F64F-1F3FC",
+ "unicode_alternates": "",
+ "name": "person with folded hands tone 2",
+ "shortname": ":pray_tone2:",
+ "category": "people",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": [
+ "highfive",
+ "hope",
+ "namaste",
+ "please",
+ "wish",
+ "pray",
+ "high five",
+ "sorrow",
+ "regret",
+ "sorry"
+ ]
+ },
+ "pray_tone3": {
+ "unicode": "1F64F-1F3FD",
+ "unicode_alternates": "",
+ "name": "person with folded hands tone 3",
+ "shortname": ":pray_tone3:",
+ "category": "people",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": [
+ "highfive",
+ "hope",
+ "namaste",
+ "please",
+ "wish",
+ "pray",
+ "high five",
+ "sorrow",
+ "regret",
+ "sorry"
+ ]
+ },
+ "pray_tone4": {
+ "unicode": "1F64F-1F3FE",
+ "unicode_alternates": "",
+ "name": "person with folded hands tone 4",
+ "shortname": ":pray_tone4:",
+ "category": "people",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": [
+ "highfive",
+ "hope",
+ "namaste",
+ "please",
+ "wish",
+ "pray",
+ "high five",
+ "sorrow",
+ "regret",
+ "sorry"
+ ]
+ },
+ "pray_tone5": {
+ "unicode": "1F64F-1F3FF",
+ "unicode_alternates": "",
+ "name": "person with folded hands tone 5",
+ "shortname": ":pray_tone5:",
+ "category": "people",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": [
+ "highfive",
+ "hope",
+ "namaste",
+ "please",
+ "wish",
+ "pray",
+ "high five",
+ "sorrow",
+ "regret",
+ "sorry"
+ ]
+ },
+ "prayer_beads": {
+ "unicode": "1F4FF",
+ "unicode_alternates": "",
+ "name": "prayer beads",
+ "shortname": ":prayer_beads:",
+ "category": "objects",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": []
+ },
"princess": {
"unicode": "1F478",
"unicode_alternates": [],
@@ -10087,9 +21934,138 @@
"category": "emoticons",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["blond", "crown", "female", "girl", "woman", "princess", "royal", "royalty", "king", "queen", "daughter", "disney", "high-maintenance"],
+ "keywords": [
+ "blond",
+ "crown",
+ "female",
+ "girl",
+ "woman",
+ "princess",
+ "royal",
+ "royalty",
+ "king",
+ "queen",
+ "daughter",
+ "disney",
+ "high-maintenance"
+ ],
"moji": "👸"
},
+ "princess_tone1": {
+ "unicode": "1F478-1F3FB",
+ "unicode_alternates": "",
+ "name": "princess tone 1",
+ "shortname": ":princess_tone1:",
+ "category": "people",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": [
+ "blond",
+ "crown",
+ "female",
+ "girl",
+ "woman",
+ "royal",
+ "royalty",
+ "king",
+ "queen",
+ "daughter",
+ "disney",
+ "high-maintenance"
+ ]
+ },
+ "princess_tone2": {
+ "unicode": "1F478-1F3FC",
+ "unicode_alternates": "",
+ "name": "princess tone 2",
+ "shortname": ":princess_tone2:",
+ "category": "people",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": [
+ "blond",
+ "crown",
+ "female",
+ "girl",
+ "woman",
+ "royal",
+ "royalty",
+ "king",
+ "queen",
+ "daughter",
+ "disney",
+ "high-maintenance"
+ ]
+ },
+ "princess_tone3": {
+ "unicode": "1F478-1F3FD",
+ "unicode_alternates": "",
+ "name": "princess tone 3",
+ "shortname": ":princess_tone3:",
+ "category": "people",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": [
+ "blond",
+ "crown",
+ "female",
+ "girl",
+ "woman",
+ "royal",
+ "royalty",
+ "king",
+ "queen",
+ "daughter",
+ "disney",
+ "high-maintenance"
+ ]
+ },
+ "princess_tone4": {
+ "unicode": "1F478-1F3FE",
+ "unicode_alternates": "",
+ "name": "princess tone 4",
+ "shortname": ":princess_tone4:",
+ "category": "people",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": [
+ "blond",
+ "crown",
+ "female",
+ "girl",
+ "woman",
+ "royal",
+ "royalty",
+ "king",
+ "queen",
+ "daughter",
+ "disney",
+ "high-maintenance"
+ ]
+ },
+ "princess_tone5": {
+ "unicode": "1F478-1F3FF",
+ "unicode_alternates": "",
+ "name": "princess tone 5",
+ "shortname": ":princess_tone5:",
+ "category": "people",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": [
+ "blond",
+ "crown",
+ "female",
+ "girl",
+ "woman",
+ "royal",
+ "royalty",
+ "king",
+ "queen",
+ "daughter",
+ "disney",
+ "high-maintenance"
+ ]
+ },
"printer": {
"unicode": "1F5A8",
"unicode_alternates": [],
@@ -10098,7 +22074,12 @@
"category": "objects_symbols",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["hardcopy", "paper", "inkjet", "laser"]
+ "keywords": [
+ "hardcopy",
+ "paper",
+ "inkjet",
+ "laser"
+ ]
},
"prohibited": {
"unicode": "1F6C7",
@@ -10106,9 +22087,19 @@
"name": "prohibited sign",
"shortname": ":prohibited:",
"category": "objects_symbols",
- "aliases": [":prohibited_sign:"],
- "aliases_ascii": [],
- "keywords": ["no", "not", "denied", "disallow", "forbid", "limit", "stop"]
+ "aliases": [
+ ":prohibited_sign:"
+ ],
+ "aliases_ascii": [],
+ "keywords": [
+ "no",
+ "not",
+ "denied",
+ "disallow",
+ "forbid",
+ "limit",
+ "stop"
+ ]
},
"projector": {
"unicode": "1F4FD",
@@ -10116,9 +22107,18 @@
"name": "film projector",
"shortname": ":projector:",
"category": "objects_symbols",
- "aliases": [":film_projector:"],
- "aliases_ascii": [],
- "keywords": ["movie", "video", "motion", "picture", "8mm", "16mm"]
+ "aliases": [
+ ":film_projector:"
+ ],
+ "aliases_ascii": [],
+ "keywords": [
+ "movie",
+ "video",
+ "motion",
+ "picture",
+ "8mm",
+ "16mm"
+ ]
},
"punch": {
"unicode": "1F44A",
@@ -10128,9 +22128,77 @@
"category": "emoticons",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["fist", "hand"],
+ "keywords": [
+ "fist",
+ "hand"
+ ],
"moji": "👊"
},
+ "punch_tone1": {
+ "unicode": "1F44A-1F3FB",
+ "unicode_alternates": "",
+ "name": "fisted hand sign tone 1",
+ "shortname": ":punch_tone1:",
+ "category": "people",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": [
+ "fist",
+ "punch"
+ ]
+ },
+ "punch_tone2": {
+ "unicode": "1F44A-1F3FC",
+ "unicode_alternates": "",
+ "name": "fisted hand sign tone 2",
+ "shortname": ":punch_tone2:",
+ "category": "people",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": [
+ "fist",
+ "punch"
+ ]
+ },
+ "punch_tone3": {
+ "unicode": "1F44A-1F3FD",
+ "unicode_alternates": "",
+ "name": "fisted hand sign tone 3",
+ "shortname": ":punch_tone3:",
+ "category": "people",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": [
+ "fist",
+ "punch"
+ ]
+ },
+ "punch_tone4": {
+ "unicode": "1F44A-1F3FE",
+ "unicode_alternates": "",
+ "name": "fisted hand sign tone 4",
+ "shortname": ":punch_tone4:",
+ "category": "people",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": [
+ "fist",
+ "punch"
+ ]
+ },
+ "punch_tone5": {
+ "unicode": "1F44A-1F3FF",
+ "unicode_alternates": "",
+ "name": "fisted hand sign tone 5",
+ "shortname": ":punch_tone5:",
+ "category": "people",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": [
+ "fist",
+ "punch"
+ ]
+ },
"purple_heart": {
"unicode": "1F49C",
"unicode_alternates": [],
@@ -10139,7 +22207,25 @@
"category": "emoticons",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["affection", "like", "love", "valentines", "purple", "violet", "heart", "love", "sensitive", "understanding", "compassionate", "compassion", "duty", "honor", "royalty", "veteran", "sacrifice"],
+ "keywords": [
+ "affection",
+ "like",
+ "love",
+ "valentines",
+ "purple",
+ "violet",
+ "heart",
+ "love",
+ "sensitive",
+ "understanding",
+ "compassionate",
+ "compassion",
+ "duty",
+ "honor",
+ "royalty",
+ "veteran",
+ "sacrifice"
+ ],
"moji": "💜"
},
"purse": {
@@ -10150,7 +22236,20 @@
"category": "emoticons",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["accessories", "fashion", "money", "purse", "clutch", "bag", "handbag", "coin bag", "accessory", "money", "ladies", "shopping"],
+ "keywords": [
+ "accessories",
+ "fashion",
+ "money",
+ "purse",
+ "clutch",
+ "bag",
+ "handbag",
+ "coin bag",
+ "accessory",
+ "money",
+ "ladies",
+ "shopping"
+ ],
"moji": "👛"
},
"pushpin": {
@@ -10161,7 +22260,9 @@
"category": "objects",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["stationery"],
+ "keywords": [
+ "stationery"
+ ],
"moji": "📌"
},
"pushpin_black": {
@@ -10172,7 +22273,9 @@
"category": "objects_symbols",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["stationery"]
+ "keywords": [
+ "stationery"
+ ]
},
"put_litter_in_its_place": {
"unicode": "1F6AE",
@@ -10182,7 +22285,15 @@
"category": "other",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["blue-square", "litter", "waste", "trash", "garbage", "receptacle", "can"],
+ "keywords": [
+ "blue-square",
+ "litter",
+ "waste",
+ "trash",
+ "garbage",
+ "receptacle",
+ "can"
+ ],
"moji": "🚮"
},
"question": {
@@ -10193,7 +22304,10 @@
"category": "other",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["confused", "doubt"],
+ "keywords": [
+ "confused",
+ "doubt"
+ ],
"moji": "❓"
},
"rabbit": {
@@ -10204,7 +22318,10 @@
"category": "nature",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["animal", "nature"],
+ "keywords": [
+ "animal",
+ "nature"
+ ],
"moji": "🐰"
},
"rabbit2": {
@@ -10215,7 +22332,15 @@
"category": "nature",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["animal", "nature", "rabbit", "bunny", "easter", "reproduction", "prolific"],
+ "keywords": [
+ "animal",
+ "nature",
+ "rabbit",
+ "bunny",
+ "easter",
+ "reproduction",
+ "prolific"
+ ],
"moji": "🐇"
},
"race_car": {
@@ -10224,9 +22349,18 @@
"name": "racing car",
"shortname": ":race_car:",
"category": "activity",
- "aliases": [":racing_car:"],
- "aliases_ascii": [],
- "keywords": ["formula 1", "race", "stock", "nascar", "speed", "drive"]
+ "aliases": [
+ ":racing_car:"
+ ],
+ "aliases_ascii": [],
+ "keywords": [
+ "formula 1",
+ "race",
+ "stock",
+ "nascar",
+ "speed",
+ "drive"
+ ]
},
"racehorse": {
"unicode": "1F40E",
@@ -10236,7 +22370,29 @@
"category": "nature",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["animal", "gamble", "horse", "powerful", "draft", "calvary", "cowboy", "cowgirl", "mounted", "race", "ride", "gallop", "trot", "colt", "filly", "mare", "stallion", "gelding", "yearling", "thoroughbred", "pony"],
+ "keywords": [
+ "animal",
+ "gamble",
+ "horse",
+ "powerful",
+ "draft",
+ "calvary",
+ "cowboy",
+ "cowgirl",
+ "mounted",
+ "race",
+ "ride",
+ "gallop",
+ "trot",
+ "colt",
+ "filly",
+ "mare",
+ "stallion",
+ "gelding",
+ "yearling",
+ "thoroughbred",
+ "pony"
+ ],
"moji": "🐎"
},
"radio": {
@@ -10247,7 +22403,12 @@
"category": "objects",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["communication", "music", "podcast", "program"],
+ "keywords": [
+ "communication",
+ "music",
+ "podcast",
+ "program"
+ ],
"moji": "📻"
},
"radio_button": {
@@ -10258,9 +22419,25 @@
"category": "other",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["input"],
+ "keywords": [
+ "input"
+ ],
"moji": "🔘"
},
+ "radioactive": {
+ "unicode": "2622",
+ "unicode_alternates": "",
+ "name": "radioactive sign",
+ "shortname": ":radioactive:",
+ "category": "symbols",
+ "aliases": [
+ ":radioactive_sign:"
+ ],
+ "aliases_ascii": [],
+ "keywords": [
+ "symbol"
+ ]
+ },
"rage": {
"unicode": "1F621",
"unicode_alternates": [],
@@ -10269,7 +22446,16 @@
"category": "emoticons",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["angry", "despise", "hate", "mad", "pout", "anger", "rage", "irate"],
+ "keywords": [
+ "angry",
+ "despise",
+ "hate",
+ "mad",
+ "pout",
+ "anger",
+ "rage",
+ "irate"
+ ],
"moji": "😡"
},
"railway_car": {
@@ -10280,7 +22466,15 @@
"category": "places",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["transportation", "vehicle", "railway", "rail", "car", "coach", "train"],
+ "keywords": [
+ "transportation",
+ "vehicle",
+ "railway",
+ "rail",
+ "car",
+ "coach",
+ "train"
+ ],
"moji": "🚃"
},
"railway_track": {
@@ -10289,9 +22483,17 @@
"name": "railway track",
"shortname": ":railway_track:",
"category": "travel_places",
- "aliases": [":railroad_track:"],
- "aliases_ascii": [],
- "keywords": ["train", "trolley", "subway", "locomotive", "transit"]
+ "aliases": [
+ ":railroad_track:"
+ ],
+ "aliases_ascii": [],
+ "keywords": [
+ "train",
+ "trolley",
+ "subway",
+ "locomotive",
+ "transit"
+ ]
},
"rainbow": {
"unicode": "1F308",
@@ -10301,7 +22503,21 @@
"category": "nature",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["happy", "nature", "photo", "sky", "unicorn", "rainbow", "color", "pride", "diversity", "spectrum", "refract", "leprechaun", "gold"],
+ "keywords": [
+ "happy",
+ "nature",
+ "photo",
+ "sky",
+ "unicorn",
+ "rainbow",
+ "color",
+ "pride",
+ "diversity",
+ "spectrum",
+ "refract",
+ "leprechaun",
+ "gold"
+ ],
"moji": "🌈"
},
"raised_hand": {
@@ -10312,9 +22528,83 @@
"category": "emoticons",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["female", "girl", "woman"],
+ "keywords": [
+ "female",
+ "girl",
+ "woman"
+ ],
"moji": "✋"
},
+ "raised_hand_tone1": {
+ "unicode": "270B-1F3FB",
+ "unicode_alternates": "",
+ "name": "raised hand tone 1",
+ "shortname": ":raised_hand_tone1:",
+ "category": "people",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": [
+ "female",
+ "girl",
+ "woman"
+ ]
+ },
+ "raised_hand_tone2": {
+ "unicode": "270B-1F3FC",
+ "unicode_alternates": "",
+ "name": "raised hand tone 2",
+ "shortname": ":raised_hand_tone2:",
+ "category": "people",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": [
+ "female",
+ "girl",
+ "woman"
+ ]
+ },
+ "raised_hand_tone3": {
+ "unicode": "270B-1F3FD",
+ "unicode_alternates": "",
+ "name": "raised hand tone 3",
+ "shortname": ":raised_hand_tone3:",
+ "category": "people",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": [
+ "female",
+ "girl",
+ "woman"
+ ]
+ },
+ "raised_hand_tone4": {
+ "unicode": "270B-1F3FE",
+ "unicode_alternates": "",
+ "name": "raised hand tone 4",
+ "shortname": ":raised_hand_tone4:",
+ "category": "people",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": [
+ "female",
+ "girl",
+ "woman"
+ ]
+ },
+ "raised_hand_tone5": {
+ "unicode": "270B-1F3FF",
+ "unicode_alternates": "",
+ "name": "raised hand tone 5",
+ "shortname": ":raised_hand_tone5:",
+ "category": "people",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": [
+ "female",
+ "girl",
+ "woman"
+ ]
+ },
"raised_hands": {
"unicode": "1F64C",
"unicode_alternates": [],
@@ -10323,9 +22613,106 @@
"category": "emoticons",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["gesture", "hooray", "winning", "woot", "yay", "banzai"],
+ "keywords": [
+ "gesture",
+ "hooray",
+ "winning",
+ "woot",
+ "yay",
+ "banzai"
+ ],
"moji": "🙌"
},
+ "raised_hands_tone1": {
+ "unicode": "1F64C-1F3FB",
+ "unicode_alternates": "",
+ "name": "person raising both hands in celebration tone 1",
+ "shortname": ":raised_hands_tone1:",
+ "category": "people",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": [
+ "gesture",
+ "hooray",
+ "winning",
+ "woot",
+ "yay",
+ "banzai",
+ "raised"
+ ]
+ },
+ "raised_hands_tone2": {
+ "unicode": "1F64C-1F3FC",
+ "unicode_alternates": "",
+ "name": "person raising both hands in celebration tone 2",
+ "shortname": ":raised_hands_tone2:",
+ "category": "people",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": [
+ "gesture",
+ "hooray",
+ "winning",
+ "woot",
+ "yay",
+ "banzai",
+ "raised"
+ ]
+ },
+ "raised_hands_tone3": {
+ "unicode": "1F64C-1F3FD",
+ "unicode_alternates": "",
+ "name": "person raising both hands in celebration tone 3",
+ "shortname": ":raised_hands_tone3:",
+ "category": "people",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": [
+ "gesture",
+ "hooray",
+ "winning",
+ "woot",
+ "yay",
+ "banzai",
+ "raised"
+ ]
+ },
+ "raised_hands_tone4": {
+ "unicode": "1F64C-1F3FE",
+ "unicode_alternates": "",
+ "name": "person raising both hands in celebration tone 4",
+ "shortname": ":raised_hands_tone4:",
+ "category": "people",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": [
+ "gesture",
+ "hooray",
+ "winning",
+ "woot",
+ "yay",
+ "banzai",
+ "raised"
+ ]
+ },
+ "raised_hands_tone5": {
+ "unicode": "1F64C-1F3FF",
+ "unicode_alternates": "",
+ "name": "person raising both hands in celebration tone 5",
+ "shortname": ":raised_hands_tone5:",
+ "category": "people",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": [
+ "gesture",
+ "hooray",
+ "winning",
+ "woot",
+ "yay",
+ "banzai",
+ "raised"
+ ]
+ },
"raising_hand": {
"unicode": "1F64B",
"unicode_alternates": [],
@@ -10334,9 +22721,108 @@
"category": "emoticons",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["female", "girl", "woman", "hand", "raise", "notice", "attention", "answer"],
+ "keywords": [
+ "female",
+ "girl",
+ "woman",
+ "hand",
+ "raise",
+ "notice",
+ "attention",
+ "answer"
+ ],
"moji": "🙋"
},
+ "raising_hand_tone1": {
+ "unicode": "1F64B-1F3FB",
+ "unicode_alternates": "",
+ "name": "happy person raising one hand tone1",
+ "shortname": ":raising_hand_tone1:",
+ "category": "people",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": [
+ "female",
+ "girl",
+ "woman",
+ "raise",
+ "notice",
+ "attention",
+ "answer"
+ ]
+ },
+ "raising_hand_tone2": {
+ "unicode": "1F64B-1F3FC",
+ "unicode_alternates": "",
+ "name": "happy person raising one hand tone2",
+ "shortname": ":raising_hand_tone2:",
+ "category": "people",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": [
+ "female",
+ "girl",
+ "woman",
+ "raise",
+ "notice",
+ "attention",
+ "answer"
+ ]
+ },
+ "raising_hand_tone3": {
+ "unicode": "1F64B-1F3FD",
+ "unicode_alternates": "",
+ "name": "happy person raising one hand tone3",
+ "shortname": ":raising_hand_tone3:",
+ "category": "people",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": [
+ "female",
+ "girl",
+ "woman",
+ "raise",
+ "notice",
+ "attention",
+ "answer"
+ ]
+ },
+ "raising_hand_tone4": {
+ "unicode": "1F64B-1F3FE",
+ "unicode_alternates": "",
+ "name": "happy person raising one hand tone4",
+ "shortname": ":raising_hand_tone4:",
+ "category": "people",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": [
+ "female",
+ "girl",
+ "woman",
+ "raise",
+ "notice",
+ "attention",
+ "answer"
+ ]
+ },
+ "raising_hand_tone5": {
+ "unicode": "1F64B-1F3FF",
+ "unicode_alternates": "",
+ "name": "happy person raising one hand tone5",
+ "shortname": ":raising_hand_tone5:",
+ "category": "people",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": [
+ "female",
+ "girl",
+ "woman",
+ "raise",
+ "notice",
+ "attention",
+ "answer"
+ ]
+ },
"ram": {
"unicode": "1F40F",
"unicode_alternates": [],
@@ -10345,7 +22831,16 @@
"category": "nature",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["animal", "nature", "sheep", "ram", "sheep", "male", "horn", "horns"],
+ "keywords": [
+ "animal",
+ "nature",
+ "sheep",
+ "ram",
+ "sheep",
+ "male",
+ "horn",
+ "horns"
+ ],
"moji": "🐏"
},
"ramen": {
@@ -10356,7 +22851,17 @@
"category": "objects",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["chipsticks", "food", "japanese", "noodle", "ramen", "noodles", "bowl", "steaming", "soup"],
+ "keywords": [
+ "chipsticks",
+ "food",
+ "japanese",
+ "noodle",
+ "ramen",
+ "noodles",
+ "bowl",
+ "steaming",
+ "soup"
+ ],
"moji": "🍜"
},
"rat": {
@@ -10367,18 +22872,45 @@
"category": "nature",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["animal", "mouse", "rat", "rodent", "crooked", "snitch"],
+ "keywords": [
+ "animal",
+ "mouse",
+ "rat",
+ "rodent",
+ "crooked",
+ "snitch"
+ ],
"moji": "🐀"
},
+ "record_button": {
+ "unicode": "23FA",
+ "unicode_alternates": "",
+ "name": "black circle for record",
+ "shortname": ":record_button:",
+ "category": "symbols",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": [
+ "sound",
+ "symbol"
+ ]
+ },
"recycle": {
"unicode": "267B",
- "unicode_alternates": ["267B-FE0F"],
+ "unicode_alternates": [
+ "267B-FE0F"
+ ],
"name": "black universal recycling symbol",
"shortname": ":recycle:",
"category": "other",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["arrow", "environment", "garbage", "trash"],
+ "keywords": [
+ "arrow",
+ "environment",
+ "garbage",
+ "trash"
+ ],
"moji": "♻"
},
"red_car": {
@@ -10389,7 +22921,10 @@
"category": "places",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["transportation", "vehicle"],
+ "keywords": [
+ "transportation",
+ "vehicle"
+ ],
"moji": "🚗"
},
"red_circle": {
@@ -10400,7 +22935,9 @@
"category": "other",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["shape"],
+ "keywords": [
+ "shape"
+ ],
"moji": "🔴"
},
"registered": {
@@ -10412,17 +22949,28 @@
"category": "other",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["alphabet", "circle"]
+ "keywords": [
+ "alphabet",
+ "circle"
+ ]
},
"relaxed": {
"unicode": "263A",
- "unicode_alternates": ["263A-FE0F"],
+ "unicode_alternates": [
+ "263A-FE0F"
+ ],
"name": "white smiling face",
"shortname": ":relaxed:",
"category": "emoticons",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["blush", "face", "happiness", "massage", "smile"],
+ "keywords": [
+ "blush",
+ "face",
+ "happiness",
+ "massage",
+ "smile"
+ ],
"moji": "☺"
},
"relieved": {
@@ -10433,7 +22981,17 @@
"category": "emoticons",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["face", "happiness", "massage", "phew", "relaxed", "relieved", "satisfied", "phew", "relief"],
+ "keywords": [
+ "face",
+ "happiness",
+ "massage",
+ "phew",
+ "relaxed",
+ "relieved",
+ "satisfied",
+ "phew",
+ "relief"
+ ],
"moji": "😌"
},
"reminder_ribbon": {
@@ -10444,7 +23002,9 @@
"category": "celebration",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["awareness"]
+ "keywords": [
+ "awareness"
+ ]
},
"repeat": {
"unicode": "1F501",
@@ -10454,7 +23014,10 @@
"category": "other",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["loop", "record"],
+ "keywords": [
+ "loop",
+ "record"
+ ],
"moji": "🔁"
},
"repeat_one": {
@@ -10465,7 +23028,10 @@
"category": "other",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["blue-square", "loop"],
+ "keywords": [
+ "blue-square",
+ "loop"
+ ],
"moji": "🔂"
},
"restroom": {
@@ -10476,7 +23042,17 @@
"category": "other",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["blue-square", "woman", "man", "unisex", "bathroom", "restroom", "sign", "shared", "toilet"],
+ "keywords": [
+ "blue-square",
+ "woman",
+ "man",
+ "unisex",
+ "bathroom",
+ "restroom",
+ "sign",
+ "shared",
+ "toilet"
+ ],
"moji": "🚻"
},
"revolving_hearts": {
@@ -10487,7 +23063,19 @@
"category": "emoticons",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["affection", "like", "love", "valentines", "heart", "hearts", "revolving", "moving", "circle", "multiple", "lovers"],
+ "keywords": [
+ "affection",
+ "like",
+ "love",
+ "valentines",
+ "heart",
+ "hearts",
+ "revolving",
+ "moving",
+ "circle",
+ "multiple",
+ "lovers"
+ ],
"moji": "💞"
},
"rewind": {
@@ -10498,7 +23086,10 @@
"category": "other",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["blue-square", "play"],
+ "keywords": [
+ "blue-square",
+ "play"
+ ],
"moji": "⏪"
},
"ribbon": {
@@ -10509,7 +23100,16 @@
"category": "emoticons",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["bowtie", "decoration", "girl", "pink", "ribbon", "lace", "wrap", "decorate"],
+ "keywords": [
+ "bowtie",
+ "decoration",
+ "girl",
+ "pink",
+ "ribbon",
+ "lace",
+ "wrap",
+ "decorate"
+ ],
"moji": "🎀"
},
"rice": {
@@ -10520,7 +23120,14 @@
"category": "objects",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["food", "rice", "white", "grain", "food", "bowl"],
+ "keywords": [
+ "food",
+ "rice",
+ "white",
+ "grain",
+ "food",
+ "bowl"
+ ],
"moji": "🍚"
},
"rice_ball": {
@@ -10531,7 +23138,16 @@
"category": "objects",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["food", "japanese", "rice", "ball", "white", "nori", "seaweed", "japanese"],
+ "keywords": [
+ "food",
+ "japanese",
+ "rice",
+ "ball",
+ "white",
+ "nori",
+ "seaweed",
+ "japanese"
+ ],
"moji": "🍙"
},
"rice_cracker": {
@@ -10542,7 +23158,15 @@
"category": "objects",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["food", "japanese", "rice", "cracker", "seaweed", "food", "japanese"],
+ "keywords": [
+ "food",
+ "japanese",
+ "rice",
+ "cracker",
+ "seaweed",
+ "food",
+ "japanese"
+ ],
"moji": "🍘"
},
"rice_scene": {
@@ -10553,7 +23177,18 @@
"category": "objects",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["photo", "moon", "viewing", "observing", "otsukimi", "tsukimi", "rice", "scene", "festival", "autumn"],
+ "keywords": [
+ "photo",
+ "moon",
+ "viewing",
+ "observing",
+ "otsukimi",
+ "tsukimi",
+ "rice",
+ "scene",
+ "festival",
+ "autumn"
+ ],
"moji": "🎑"
},
"right_speaker": {
@@ -10564,7 +23199,13 @@
"category": "objects_symbols",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["sound", "listen", "hear", "noise", "volume"]
+ "keywords": [
+ "sound",
+ "listen",
+ "hear",
+ "noise",
+ "volume"
+ ]
},
"right_speaker_one": {
"unicode": "1F569",
@@ -10572,9 +23213,14 @@
"name": "right speaker with one sound wave",
"shortname": ":right_speaker_one:",
"category": "objects_symbols",
- "aliases": [":right_speaker_with_one_sound_wave:"],
+ "aliases": [
+ ":right_speaker_with_one_sound_wave:"
+ ],
"aliases_ascii": [],
- "keywords": ["low", "volume"]
+ "keywords": [
+ "low",
+ "volume"
+ ]
},
"right_speaker_three": {
"unicode": "1F56A",
@@ -10582,9 +23228,15 @@
"name": "right speaker with three sound waves",
"shortname": ":right_speaker_three:",
"category": "objects_symbols",
- "aliases": [":right_speaker_with_three_sound_waves:"],
+ "aliases": [
+ ":right_speaker_with_three_sound_waves:"
+ ],
"aliases_ascii": [],
- "keywords": ["loud", "high", "volume"]
+ "keywords": [
+ "loud",
+ "high",
+ "volume"
+ ]
},
"ring": {
"unicode": "1F48D",
@@ -10594,7 +23246,12 @@
"category": "emoticons",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["marriage", "propose", "valentines", "wedding"],
+ "keywords": [
+ "marriage",
+ "propose",
+ "valentines",
+ "wedding"
+ ],
"moji": "💍"
},
"ringing_bell": {
@@ -10605,7 +23262,25 @@
"category": "objects_symbols",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["alert", "ding", "volume", "sound", "chime"]
+ "keywords": [
+ "alert",
+ "ding",
+ "volume",
+ "sound",
+ "chime"
+ ]
+ },
+ "robot": {
+ "unicode": "1F916",
+ "unicode_alternates": "",
+ "name": "robot face",
+ "shortname": ":robot:",
+ "category": "people",
+ "aliases": [
+ ":robot_face:"
+ ],
+ "aliases_ascii": [],
+ "keywords": []
},
"rocket": {
"unicode": "1F680",
@@ -10615,7 +23290,16 @@
"category": "places",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["launch", "ship", "staffmode", "rocket", "space", "spacecraft", "astronaut", "cosmonaut"],
+ "keywords": [
+ "launch",
+ "ship",
+ "staffmode",
+ "rocket",
+ "space",
+ "spacecraft",
+ "astronaut",
+ "cosmonaut"
+ ],
"moji": "🚀"
},
"roller_coaster": {
@@ -10626,9 +23310,34 @@
"category": "places",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["carnival", "fun", "photo", "play", "playground", "roller", "coaster", "amusement", "park", "fair", "ride", "entertainment"],
+ "keywords": [
+ "carnival",
+ "fun",
+ "photo",
+ "play",
+ "playground",
+ "roller",
+ "coaster",
+ "amusement",
+ "park",
+ "fair",
+ "ride",
+ "entertainment"
+ ],
"moji": "🎢"
},
+ "rolling_eyes": {
+ "unicode": "1F644",
+ "unicode_alternates": "",
+ "name": "face with rolling eyes",
+ "shortname": ":rolling_eyes:",
+ "category": "people",
+ "aliases": [
+ ":face_with_rolling_eyes:"
+ ],
+ "aliases_ascii": [],
+ "keywords": []
+ },
"rooster": {
"unicode": "1F413",
"unicode_alternates": [],
@@ -10637,7 +23346,17 @@
"category": "nature",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["animal", "chicken", "nature", "rooster", "cockerel", "cock", "male", "cock-a-doodle-doo", "crowing"],
+ "keywords": [
+ "animal",
+ "chicken",
+ "nature",
+ "rooster",
+ "cockerel",
+ "cock",
+ "male",
+ "cock-a-doodle-doo",
+ "crowing"
+ ],
"moji": "🐓"
},
"rose": {
@@ -10648,7 +23367,18 @@
"category": "nature",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["flowers", "love", "valentines", "rose", "fragrant", "flower", "thorns", "love", "petals", "romance"],
+ "keywords": [
+ "flowers",
+ "love",
+ "valentines",
+ "rose",
+ "fragrant",
+ "flower",
+ "thorns",
+ "love",
+ "petals",
+ "romance"
+ ],
"moji": "🌹"
},
"rosette": {
@@ -10659,7 +23389,9 @@
"category": "objects_symbols",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["flower"]
+ "keywords": [
+ "flower"
+ ]
},
"rosette_black": {
"unicode": "1F3F6",
@@ -10669,7 +23401,9 @@
"category": "objects_symbols",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["flower"]
+ "keywords": [
+ "flower"
+ ]
},
"rotating_light": {
"unicode": "1F6A8",
@@ -10679,7 +23413,15 @@
"category": "places",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["911", "ambulance", "emergency", "police", "light", "police", "emergency"],
+ "keywords": [
+ "911",
+ "ambulance",
+ "emergency",
+ "police",
+ "light",
+ "police",
+ "emergency"
+ ],
"moji": "🚨"
},
"round_pushpin": {
@@ -10690,7 +23432,9 @@
"category": "places",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["stationery"],
+ "keywords": [
+ "stationery"
+ ],
"moji": "📍"
},
"rowboat": {
@@ -10701,9 +23445,108 @@
"category": "places",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["hobby", "ship", "sports", "water", "boat", "row", "oar", "paddle"],
+ "keywords": [
+ "hobby",
+ "ship",
+ "sports",
+ "water",
+ "boat",
+ "row",
+ "oar",
+ "paddle"
+ ],
"moji": "🚣"
},
+ "rowboat_tone1": {
+ "unicode": "1F6A3-1F3FB",
+ "unicode_alternates": "",
+ "name": "rowboat tone 1",
+ "shortname": ":rowboat_tone1:",
+ "category": "activity",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": [
+ "hobby",
+ "ship",
+ "water",
+ "boat",
+ "row",
+ "oar",
+ "paddle"
+ ]
+ },
+ "rowboat_tone2": {
+ "unicode": "1F6A3-1F3FC",
+ "unicode_alternates": "",
+ "name": "rowboat tone 2",
+ "shortname": ":rowboat_tone2:",
+ "category": "activity",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": [
+ "hobby",
+ "ship",
+ "water",
+ "boat",
+ "row",
+ "oar",
+ "paddle"
+ ]
+ },
+ "rowboat_tone3": {
+ "unicode": "1F6A3-1F3FD",
+ "unicode_alternates": "",
+ "name": "rowboat tone 3",
+ "shortname": ":rowboat_tone3:",
+ "category": "activity",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": [
+ "hobby",
+ "ship",
+ "water",
+ "boat",
+ "row",
+ "oar",
+ "paddle"
+ ]
+ },
+ "rowboat_tone4": {
+ "unicode": "1F6A3-1F3FE",
+ "unicode_alternates": "",
+ "name": "rowboat tone 4",
+ "shortname": ":rowboat_tone4:",
+ "category": "activity",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": [
+ "hobby",
+ "ship",
+ "water",
+ "boat",
+ "row",
+ "oar",
+ "paddle"
+ ]
+ },
+ "rowboat_tone5": {
+ "unicode": "1F6A3-1F3FF",
+ "unicode_alternates": "",
+ "name": "rowboat tone 5",
+ "shortname": ":rowboat_tone5:",
+ "category": "activity",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": [
+ "hobby",
+ "ship",
+ "water",
+ "boat",
+ "row",
+ "oar",
+ "paddle"
+ ]
+ },
"rugby_football": {
"unicode": "1F3C9",
"unicode_alternates": [],
@@ -10712,7 +23555,15 @@
"category": "objects",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["sports", "rugby", "football", "ball", "sport", "team", "england"],
+ "keywords": [
+ "sports",
+ "rugby",
+ "football",
+ "ball",
+ "sport",
+ "team",
+ "england"
+ ],
"moji": "🏉"
},
"runner": {
@@ -10723,9 +23574,115 @@
"category": "emoticons",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["exercise", "man", "walking", "run", "runner", "jog", "exercise", "sprint", "race", "dash"],
+ "keywords": [
+ "exercise",
+ "man",
+ "walking",
+ "run",
+ "runner",
+ "jog",
+ "exercise",
+ "sprint",
+ "race",
+ "dash"
+ ],
"moji": "🏃"
},
+ "runner_tone1": {
+ "unicode": "1F3C3-1F3FB",
+ "unicode_alternates": "",
+ "name": "runner tone 1",
+ "shortname": ":runner_tone1:",
+ "category": "people",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": [
+ "exercise",
+ "man",
+ "run",
+ "jog",
+ "sprint",
+ "race",
+ "dash",
+ "marathon"
+ ]
+ },
+ "runner_tone2": {
+ "unicode": "1F3C3-1F3FC",
+ "unicode_alternates": "",
+ "name": "runner tone 2",
+ "shortname": ":runner_tone2:",
+ "category": "people",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": [
+ "exercise",
+ "man",
+ "run",
+ "jog",
+ "sprint",
+ "race",
+ "dash",
+ "marathon"
+ ]
+ },
+ "runner_tone3": {
+ "unicode": "1F3C3-1F3FD",
+ "unicode_alternates": "",
+ "name": "runner tone 3",
+ "shortname": ":runner_tone3:",
+ "category": "people",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": [
+ "exercise",
+ "man",
+ "run",
+ "jog",
+ "sprint",
+ "race",
+ "dash",
+ "marathon"
+ ]
+ },
+ "runner_tone4": {
+ "unicode": "1F3C3-1F3FE",
+ "unicode_alternates": "",
+ "name": "runner tone 4",
+ "shortname": ":runner_tone4:",
+ "category": "people",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": [
+ "exercise",
+ "man",
+ "run",
+ "jog",
+ "sprint",
+ "race",
+ "dash",
+ "marathon"
+ ]
+ },
+ "runner_tone5": {
+ "unicode": "1F3C3-1F3FF",
+ "unicode_alternates": "",
+ "name": "runner tone 5",
+ "shortname": ":runner_tone5:",
+ "category": "people",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": [
+ "exercise",
+ "man",
+ "run",
+ "jog",
+ "sprint",
+ "race",
+ "dash",
+ "marathon"
+ ]
+ },
"running_shirt_with_sash": {
"unicode": "1F3BD",
"unicode_alternates": [],
@@ -10734,29 +23691,73 @@
"category": "emoticons",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["pageant", "play", "running", "run", "shirt", "cloths", "compete", "sports"],
+ "keywords": [
+ "pageant",
+ "play",
+ "running",
+ "run",
+ "shirt",
+ "cloths",
+ "compete",
+ "sports"
+ ],
"moji": "🎽"
},
+ "sa": {
+ "unicode": "1F202",
+ "unicode_alternates": "",
+ "name": "squared katakana sa",
+ "shortname": ":sa:",
+ "category": "symbols",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": [
+ "blue-square",
+ "japanese",
+ "symbol",
+ "word"
+ ]
+ },
"sagittarius": {
"unicode": "2650",
- "unicode_alternates": ["2650-FE0F"],
+ "unicode_alternates": [
+ "2650-FE0F"
+ ],
"name": "sagittarius",
"shortname": ":sagittarius:",
"category": "other",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["sagittarius", "centaur", "archer", "astrology", "greek", "constellation", "stars", "zodiac", "sign", "sign", "zodiac", "horoscope"],
+ "keywords": [
+ "sagittarius",
+ "centaur",
+ "archer",
+ "astrology",
+ "greek",
+ "constellation",
+ "stars",
+ "zodiac",
+ "sign",
+ "sign",
+ "zodiac",
+ "horoscope"
+ ],
"moji": "♐"
},
"sailboat": {
"unicode": "26F5",
- "unicode_alternates": ["26F5-FE0F"],
+ "unicode_alternates": [
+ "26F5-FE0F"
+ ],
"name": "sailboat",
"shortname": ":sailboat:",
"category": "places",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["ship", "transportation"],
+ "keywords": [
+ "ship",
+ "transportation"
+ ],
"moji": "⛵"
},
"sake": {
@@ -10767,7 +23768,19 @@
"category": "objects",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["beverage", "drink", "drunk", "wine", "sake", "wine", "rice", "ferment", "alcohol", "japanese", "drink"],
+ "keywords": [
+ "beverage",
+ "drink",
+ "drunk",
+ "wine",
+ "sake",
+ "wine",
+ "rice",
+ "ferment",
+ "alcohol",
+ "japanese",
+ "drink"
+ ],
"moji": "🍶"
},
"sandal": {
@@ -10778,7 +23791,10 @@
"category": "emoticons",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["fashion", "shoes"],
+ "keywords": [
+ "fashion",
+ "shoes"
+ ],
"moji": "👡"
},
"santa": {
@@ -10789,9 +23805,159 @@
"category": "objects",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["christmas", "father christmas", "festival", "male", "man", "xmas", "santa", "saint nick", "jolly", "ho ho ho", "north pole", "presents", "gifts", "naughty", "nice", "sleigh", "father", "christmas", "holiday"],
+ "keywords": [
+ "christmas",
+ "father christmas",
+ "festival",
+ "male",
+ "man",
+ "xmas",
+ "santa",
+ "saint nick",
+ "jolly",
+ "ho ho ho",
+ "north pole",
+ "presents",
+ "gifts",
+ "naughty",
+ "nice",
+ "sleigh",
+ "father",
+ "christmas",
+ "holiday"
+ ],
"moji": "🎅"
},
+ "santa_tone1": {
+ "unicode": "1F385-1F3FB",
+ "unicode_alternates": "",
+ "name": "father christmas tone 1",
+ "shortname": ":santa_tone1:",
+ "category": "people",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": [
+ "festival",
+ "male",
+ "man",
+ "xmas",
+ "santa",
+ "saint nick",
+ "jolly",
+ "ho ho ho",
+ "north pole",
+ "presents",
+ "gifts",
+ "naughty",
+ "nice",
+ "sleigh",
+ "holiday"
+ ]
+ },
+ "santa_tone2": {
+ "unicode": "1F385-1F3FC",
+ "unicode_alternates": "",
+ "name": "father christmas tone 2",
+ "shortname": ":santa_tone2:",
+ "category": "people",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": [
+ "festival",
+ "male",
+ "man",
+ "xmas",
+ "santa",
+ "saint nick",
+ "jolly",
+ "ho ho ho",
+ "north pole",
+ "presents",
+ "gifts",
+ "naughty",
+ "nice",
+ "sleigh",
+ "holiday"
+ ]
+ },
+ "santa_tone3": {
+ "unicode": "1F385-1F3FD",
+ "unicode_alternates": "",
+ "name": "father christmas tone 3",
+ "shortname": ":santa_tone3:",
+ "category": "people",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": [
+ "festival",
+ "male",
+ "man",
+ "xmas",
+ "santa",
+ "saint nick",
+ "jolly",
+ "ho ho ho",
+ "north pole",
+ "presents",
+ "gifts",
+ "naughty",
+ "nice",
+ "sleigh",
+ "holiday"
+ ]
+ },
+ "santa_tone4": {
+ "unicode": "1F385-1F3FE",
+ "unicode_alternates": "",
+ "name": "father christmas tone 4",
+ "shortname": ":santa_tone4:",
+ "category": "people",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": [
+ "festival",
+ "male",
+ "man",
+ "xmas",
+ "santa",
+ "saint nick",
+ "jolly",
+ "ho ho ho",
+ "north pole",
+ "presents",
+ "gifts",
+ "naughty",
+ "nice",
+ "sleigh",
+ "holiday"
+ ]
+ },
+ "santa_tone5": {
+ "unicode": "1F385-1F3FF",
+ "unicode_alternates": "",
+ "name": "father christmas tone 5",
+ "shortname": ":santa_tone5:",
+ "category": "people",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": [
+ "festival",
+ "male",
+ "man",
+ "xmas",
+ "santa",
+ "saint nick",
+ "jolly",
+ "ho ho ho",
+ "north pole",
+ "presents",
+ "gifts",
+ "naughty",
+ "nice",
+ "sleigh",
+ "holiday"
+ ]
+ },
"satellite": {
"unicode": "1F4E1",
"unicode_alternates": [],
@@ -10800,7 +23966,9 @@
"category": "objects",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["communication"],
+ "keywords": [
+ "communication"
+ ],
"moji": "📡"
},
"satellite_orbital": {
@@ -10811,7 +23979,11 @@
"category": "objects_symbols",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["communication", "orbital", "space"]
+ "keywords": [
+ "communication",
+ "orbital",
+ "space"
+ ]
},
"saxophone": {
"unicode": "1F3B7",
@@ -10821,9 +23993,35 @@
"category": "objects",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["instrument", "music", "saxophone", "sax", "music", "instrument", "woodwind"],
+ "keywords": [
+ "instrument",
+ "music",
+ "saxophone",
+ "sax",
+ "music",
+ "instrument",
+ "woodwind"
+ ],
"moji": "🎷"
},
+ "scales": {
+ "unicode": "2696",
+ "unicode_alternates": "",
+ "name": "scales",
+ "shortname": ":scales:",
+ "category": "objects",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": [
+ "balance",
+ "justice",
+ "libra",
+ "object",
+ "tool",
+ "weight",
+ "zodiac"
+ ]
+ },
"school": {
"unicode": "1F3EB",
"unicode_alternates": [],
@@ -10832,7 +24030,17 @@
"category": "places",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["building", "school", "university", "elementary", "middle", "high", "college", "teach", "education"],
+ "keywords": [
+ "building",
+ "school",
+ "university",
+ "elementary",
+ "middle",
+ "high",
+ "college",
+ "teach",
+ "education"
+ ],
"moji": "🏫"
},
"school_satchel": {
@@ -10843,29 +24051,74 @@
"category": "objects",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["bag", "education", "student", "school", "satchel", "backpack", "bag", "packing", "pack", "hike", "education", "adventure", "travel", "sightsee"],
+ "keywords": [
+ "bag",
+ "education",
+ "student",
+ "school",
+ "satchel",
+ "backpack",
+ "bag",
+ "packing",
+ "pack",
+ "hike",
+ "education",
+ "adventure",
+ "travel",
+ "sightsee"
+ ],
"moji": "🎒"
},
"scissors": {
"unicode": "2702",
- "unicode_alternates": ["2702-FE0F"],
+ "unicode_alternates": [
+ "2702-FE0F"
+ ],
"name": "black scissors",
"shortname": ":scissors:",
"category": "objects",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["cut", "stationery"],
+ "keywords": [
+ "cut",
+ "stationery"
+ ],
"moji": "✂"
},
+ "scorpion": {
+ "unicode": "1F982",
+ "unicode_alternates": "",
+ "name": "scorpion",
+ "shortname": ":scorpion:",
+ "category": "nature",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": []
+ },
"scorpius": {
"unicode": "264F",
- "unicode_alternates": ["264F-FE0F"],
+ "unicode_alternates": [
+ "264F-FE0F"
+ ],
"name": "scorpius",
"shortname": ":scorpius:",
"category": "other",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["scorpius", "scorpion", "scorpio", "astrology", "greek", "constellation", "stars", "zodiac", "sign", "sign", "zodiac", "horoscope"],
+ "keywords": [
+ "scorpius",
+ "scorpion",
+ "scorpio",
+ "astrology",
+ "greek",
+ "constellation",
+ "stars",
+ "zodiac",
+ "sign",
+ "sign",
+ "zodiac",
+ "horoscope"
+ ],
"moji": "♏"
},
"scream": {
@@ -10876,7 +24129,14 @@
"category": "emoticons",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["face", "munch", "scream", "painting", "artist", "alien"],
+ "keywords": [
+ "face",
+ "munch",
+ "scream",
+ "painting",
+ "artist",
+ "alien"
+ ],
"moji": "😱"
},
"scream_cat": {
@@ -10887,7 +24147,22 @@
"category": "emoticons",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["animal", "cats", "munch", "weary", "sleepy", "tired", "tiredness", "study", "finals", "school", "exhausted", "scream", "painting", "artist"],
+ "keywords": [
+ "animal",
+ "cats",
+ "munch",
+ "weary",
+ "sleepy",
+ "tired",
+ "tiredness",
+ "study",
+ "finals",
+ "school",
+ "exhausted",
+ "scream",
+ "painting",
+ "artist"
+ ],
"moji": "🙀"
},
"scroll": {
@@ -10898,7 +24173,9 @@
"category": "objects",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["documents"],
+ "keywords": [
+ "documents"
+ ],
"moji": "📜"
},
"seat": {
@@ -10909,18 +24186,24 @@
"category": "places",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["sit"],
+ "keywords": [
+ "sit"
+ ],
"moji": "💺"
},
"secret": {
"unicode": "3299",
- "unicode_alternates": ["3299-FE0F"],
+ "unicode_alternates": [
+ "3299-FE0F"
+ ],
"name": "circled ideograph secret",
"shortname": ":secret:",
"category": "other",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["privacy"],
+ "keywords": [
+ "privacy"
+ ],
"moji": "㊙"
},
"see_no_evil": {
@@ -10931,7 +24214,17 @@
"category": "emoticons",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["animal", "monkey", "nature", "monkey", "see", "eyes", "vision", "sight", "mizaru"],
+ "keywords": [
+ "animal",
+ "monkey",
+ "nature",
+ "monkey",
+ "see",
+ "eyes",
+ "vision",
+ "sight",
+ "mizaru"
+ ],
"moji": "🙈"
},
"seedling": {
@@ -10942,19 +24235,49 @@
"category": "nature",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["grass", "lawn", "nature", "plant", "seedling", "plant", "new", "start", "grow"],
+ "keywords": [
+ "grass",
+ "lawn",
+ "nature",
+ "plant",
+ "seedling",
+ "plant",
+ "new",
+ "start",
+ "grow"
+ ],
"moji": "🌱"
},
"seven": {
"moji": "7️⃣",
"unicode": "0037-20E3",
- "unicode_alternates": ["0037-FE0F-20E3"],
+ "unicode_alternates": [
+ "0037-FE0F-20E3"
+ ],
"name": "digit seven",
"shortname": ":seven:",
"category": "other",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["7", "blue-square", "numbers", "prime"]
+ "keywords": [
+ "7",
+ "blue-square",
+ "numbers",
+ "prime"
+ ]
+ },
+ "shamrock": {
+ "unicode": "2618",
+ "unicode_alternates": "",
+ "name": "shamrock",
+ "shortname": ":shamrock:",
+ "category": "nature",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": [
+ "nature",
+ "plant"
+ ]
},
"shaved_ice": {
"unicode": "1F367",
@@ -10964,7 +24287,16 @@
"category": "objects",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["desert", "hot", "shaved", "ice", "dessert", "treat", "syrup", "flavoring"],
+ "keywords": [
+ "desert",
+ "hot",
+ "shaved",
+ "ice",
+ "dessert",
+ "treat",
+ "syrup",
+ "flavoring"
+ ],
"moji": "🍧"
},
"sheep": {
@@ -10975,7 +24307,17 @@
"category": "nature",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["animal", "nature", "sheep", "wool", "flock", "follower", "ewe", "female", "lamb"],
+ "keywords": [
+ "animal",
+ "nature",
+ "sheep",
+ "wool",
+ "flock",
+ "follower",
+ "ewe",
+ "female",
+ "lamb"
+ ],
"moji": "🐑"
},
"shell": {
@@ -10986,7 +24328,17 @@
"category": "nature",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["beach", "nature", "sea", "shell", "spiral", "beach", "sand", "crab", "nautilus"],
+ "keywords": [
+ "beach",
+ "nature",
+ "sea",
+ "shell",
+ "spiral",
+ "beach",
+ "sand",
+ "crab",
+ "nautilus"
+ ],
"moji": "🐚"
},
"shield": {
@@ -10997,7 +24349,26 @@
"category": "objects_symbols",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["interstate", "route", "sign", "highway", "interstate"]
+ "keywords": [
+ "interstate",
+ "route",
+ "sign",
+ "highway",
+ "interstate"
+ ]
+ },
+ "shinto_shrine": {
+ "unicode": "26E9",
+ "unicode_alternates": "",
+ "name": "shinto shrine",
+ "shortname": ":shinto_shrine:",
+ "category": "travel",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": [
+ "religion",
+ "symbol"
+ ]
},
"ship": {
"unicode": "1F6A2",
@@ -11007,7 +24378,13 @@
"category": "places",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["titanic", "transportation", "ferry", "ship", "boat"],
+ "keywords": [
+ "titanic",
+ "transportation",
+ "ferry",
+ "ship",
+ "boat"
+ ],
"moji": "🚢"
},
"shirt": {
@@ -11018,7 +24395,10 @@
"category": "emoticons",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["cloth", "fashion"],
+ "keywords": [
+ "cloth",
+ "fashion"
+ ],
"moji": "👕"
},
"shopping_bags": {
@@ -11029,7 +24409,13 @@
"category": "travel_places",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["purchase", "mall", "buy", "store", "shop"]
+ "keywords": [
+ "purchase",
+ "mall",
+ "buy",
+ "store",
+ "shop"
+ ]
},
"shower": {
"unicode": "1F6BF",
@@ -11039,7 +24425,18 @@
"category": "objects",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["bath", "clean", "wash", "bathroom", "shower", "soap", "water", "clean", "shampoo", "lather"],
+ "keywords": [
+ "bath",
+ "clean",
+ "wash",
+ "bathroom",
+ "shower",
+ "soap",
+ "water",
+ "clean",
+ "shampoo",
+ "lather"
+ ],
"moji": "🚿"
},
"signal_strength": {
@@ -11050,19 +24447,27 @@
"category": "other",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["blue-square"],
+ "keywords": [
+ "blue-square"
+ ],
"moji": "📶"
},
"six": {
"moji": "6️⃣",
"unicode": "0036-20E3",
- "unicode_alternates": ["0036-FE0F-20E3"],
+ "unicode_alternates": [
+ "0036-FE0F-20E3"
+ ],
"name": "digit six",
"shortname": ":six:",
"category": "other",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["6", "blue-square", "numbers"]
+ "keywords": [
+ "6",
+ "blue-square",
+ "numbers"
+ ]
},
"six_pointed_star": {
"unicode": "1F52F",
@@ -11072,7 +24477,9 @@
"category": "other",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["purple-square"],
+ "keywords": [
+ "purple-square"
+ ],
"moji": "🔯"
},
"ski": {
@@ -11083,20 +24490,75 @@
"category": "objects",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["cold", "sports", "winter", "ski", "downhill", "cross-country", "poles", "snow", "winter", "mountain", "alpine", "powder", "slalom", "freestyle"],
+ "keywords": [
+ "cold",
+ "sports",
+ "winter",
+ "ski",
+ "downhill",
+ "cross-country",
+ "poles",
+ "snow",
+ "winter",
+ "mountain",
+ "alpine",
+ "powder",
+ "slalom",
+ "freestyle"
+ ],
"moji": "🎿"
},
+ "skier": {
+ "unicode": "26F7",
+ "unicode_alternates": "",
+ "name": "skier",
+ "shortname": ":skier:",
+ "category": "activity",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": [
+ "person",
+ "ski",
+ "snow",
+ "sport",
+ "travel"
+ ]
+ },
"skull": {
"unicode": "1F480",
"unicode_alternates": [],
"name": "skull",
"shortname": ":skull:",
"category": "emoticons",
- "aliases": [":skeleton:"],
- "aliases_ascii": [],
- "keywords": ["dead", "skeleton", "dying"],
+ "aliases": [
+ ":skeleton:"
+ ],
+ "aliases_ascii": [],
+ "keywords": [
+ "dead",
+ "skeleton",
+ "dying"
+ ],
"moji": "💀"
},
+ "skull_crossbones": {
+ "unicode": "2620",
+ "unicode_alternates": "",
+ "name": "skull and crossbones",
+ "shortname": ":skull_crossbones:",
+ "category": "objects",
+ "aliases": [
+ ":skull_and_crossbones:"
+ ],
+ "aliases_ascii": [],
+ "keywords": [
+ "body",
+ "death",
+ "face",
+ "monster",
+ "person"
+ ]
+ },
"sleeping": {
"unicode": "1F634",
"unicode_alternates": [],
@@ -11105,7 +24567,15 @@
"category": "emoticons",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["face", "sleepy", "tired", "sleep", "sleepy", "sleeping", "snore"],
+ "keywords": [
+ "face",
+ "sleepy",
+ "tired",
+ "sleep",
+ "sleepy",
+ "sleeping",
+ "snore"
+ ],
"moji": "😴"
},
"sleeping_accommodation": {
@@ -11116,7 +24586,11 @@
"category": "objects_symbols",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["hotel", "motel", "rest"]
+ "keywords": [
+ "hotel",
+ "motel",
+ "rest"
+ ]
},
"sleepy": {
"unicode": "1F62A",
@@ -11126,7 +24600,14 @@
"category": "emoticons",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["face", "rest", "tired", "sleepy", "tired", "exhausted"],
+ "keywords": [
+ "face",
+ "rest",
+ "tired",
+ "sleepy",
+ "tired",
+ "exhausted"
+ ],
"moji": "😪"
},
"slight_frown": {
@@ -11135,9 +24616,16 @@
"name": "slightly frowning face",
"shortname": ":slight_frown:",
"category": "people",
- "aliases": [":slightly_frowning_face:"],
+ "aliases": [
+ ":slightly_frowning_face:"
+ ],
"aliases_ascii": [],
- "keywords": ["slight", "frown", "unhappy", "disappointed"]
+ "keywords": [
+ "slight",
+ "frown",
+ "unhappy",
+ "disappointed"
+ ]
},
"slight_smile": {
"unicode": "1F642",
@@ -11145,9 +24633,15 @@
"name": "slightly smiling face",
"shortname": ":slight_smile:",
"category": "people",
- "aliases": [":slightly_smiling_face:"],
+ "aliases": [
+ ":slightly_smiling_face:"
+ ],
"aliases_ascii": [],
- "keywords": ["slight", "smile", "happy"]
+ "keywords": [
+ "slight",
+ "smile",
+ "happy"
+ ]
},
"slot_machine": {
"unicode": "1F3B0",
@@ -11157,7 +24651,17 @@
"category": "places",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["bet", "gamble", "vegas", "slot", "machine", "gamble", "one-armed bandit", "slots", "luck"],
+ "keywords": [
+ "bet",
+ "gamble",
+ "vegas",
+ "slot",
+ "machine",
+ "gamble",
+ "one-armed bandit",
+ "slots",
+ "luck"
+ ],
"moji": "🎰"
},
"small_blue_diamond": {
@@ -11168,7 +24672,9 @@
"category": "other",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["shape"],
+ "keywords": [
+ "shape"
+ ],
"moji": "🔹"
},
"small_orange_diamond": {
@@ -11179,7 +24685,9 @@
"category": "other",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["shape"],
+ "keywords": [
+ "shape"
+ ],
"moji": "🔸"
},
"small_red_triangle": {
@@ -11190,7 +24698,9 @@
"category": "other",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["shape"],
+ "keywords": [
+ "shape"
+ ],
"moji": "🔺"
},
"small_red_triangle_down": {
@@ -11201,7 +24711,9 @@
"category": "other",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["shape"],
+ "keywords": [
+ "shape"
+ ],
"moji": "🔻"
},
"smile": {
@@ -11211,8 +24723,24 @@
"shortname": ":smile:",
"category": "emoticons",
"aliases": [],
- "aliases_ascii": [":)", ":-)", "=]", "=)", ":]"],
- "keywords": ["face", "funny", "haha", "happy", "joy", "laugh", "smile", "smiley", "smiling"],
+ "aliases_ascii": [
+ ":)",
+ ":-)",
+ "=]",
+ "=)",
+ ":]"
+ ],
+ "keywords": [
+ "face",
+ "funny",
+ "haha",
+ "happy",
+ "joy",
+ "laugh",
+ "smile",
+ "smiley",
+ "smiling"
+ ],
"moji": "😄"
},
"smile_cat": {
@@ -11223,7 +24751,14 @@
"category": "emoticons",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["animal", "cats", "cat", "smile", "grin", "grinning"],
+ "keywords": [
+ "animal",
+ "cats",
+ "cat",
+ "smile",
+ "grin",
+ "grinning"
+ ],
"moji": "😸"
},
"smiley": {
@@ -11233,8 +24768,20 @@
"shortname": ":smiley:",
"category": "emoticons",
"aliases": [],
- "aliases_ascii": [":D", ":-D", "=D"],
- "keywords": ["face", "haha", "happy", "joy", "smiling", "smile", "smiley"],
+ "aliases_ascii": [
+ ":D",
+ ":-D",
+ "=D"
+ ],
+ "keywords": [
+ "face",
+ "haha",
+ "happy",
+ "joy",
+ "smiling",
+ "smile",
+ "smiley"
+ ],
"moji": "😃"
},
"smiley_cat": {
@@ -11245,7 +24792,15 @@
"category": "emoticons",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["animal", "cats", "happy", "smile", "smiley", "cat", "happy"],
+ "keywords": [
+ "animal",
+ "cats",
+ "happy",
+ "smile",
+ "smiley",
+ "cat",
+ "happy"
+ ],
"moji": "😺"
},
"smiling_imp": {
@@ -11256,7 +24811,14 @@
"category": "emoticons",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["devil", "horns", "horns", "devil", "impish", "trouble"],
+ "keywords": [
+ "devil",
+ "horns",
+ "horns",
+ "devil",
+ "impish",
+ "trouble"
+ ],
"moji": "😈"
},
"smirk": {
@@ -11267,7 +24829,18 @@
"category": "emoticons",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["mean", "prank", "smile", "smug", "smirking", "smirk", "smug", "smile", "half-smile", "conceited"],
+ "keywords": [
+ "mean",
+ "prank",
+ "smile",
+ "smug",
+ "smirking",
+ "smirk",
+ "smug",
+ "smile",
+ "half-smile",
+ "conceited"
+ ],
"moji": "😏"
},
"smirk_cat": {
@@ -11278,7 +24851,15 @@
"category": "emoticons",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["animal", "cats", "smirk", "smirking", "wry", "confident", "confidence"],
+ "keywords": [
+ "animal",
+ "cats",
+ "smirk",
+ "smirking",
+ "wry",
+ "confident",
+ "confidence"
+ ],
"moji": "😼"
},
"smoking": {
@@ -11289,7 +24870,19 @@
"category": "objects",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["cigarette", "kills", "tobacco", "smoking", "cigarette", "smoke", "cancer", "lungs", "inhale", "tar", "nicotine"],
+ "keywords": [
+ "cigarette",
+ "kills",
+ "tobacco",
+ "smoking",
+ "cigarette",
+ "smoke",
+ "cancer",
+ "lungs",
+ "inhale",
+ "tar",
+ "nicotine"
+ ],
"moji": "🚬"
},
"snail": {
@@ -11300,7 +24893,16 @@
"category": "nature",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["animal", "shell", "slow", "snail", "slow", "escargot", "french", "appetizer"],
+ "keywords": [
+ "animal",
+ "shell",
+ "slow",
+ "snail",
+ "slow",
+ "escargot",
+ "french",
+ "appetizer"
+ ],
"moji": "🐌"
},
"snake": {
@@ -11311,7 +24913,10 @@
"category": "nature",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["animal", "evil"],
+ "keywords": [
+ "animal",
+ "evil"
+ ],
"moji": "🐍"
},
"snowboarder": {
@@ -11322,31 +24927,89 @@
"category": "objects",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["sports", "winter", "snow", "boarding", "sports", "freestyle", "halfpipe", "board", "mountain", "alpine", "winter"],
+ "keywords": [
+ "sports",
+ "winter",
+ "snow",
+ "boarding",
+ "sports",
+ "freestyle",
+ "halfpipe",
+ "board",
+ "mountain",
+ "alpine",
+ "winter"
+ ],
"moji": "🏂"
},
"snowflake": {
"unicode": "2744",
- "unicode_alternates": ["2744-FE0F"],
+ "unicode_alternates": [
+ "2744-FE0F"
+ ],
"name": "snowflake",
"shortname": ":snowflake:",
"category": "nature",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["christmas", "cold", "season", "weather", "winter", "xmas", "snowflake", "snow", "frozen", "droplet", "ice", "crystal", "cold", "chilly", "winter", "unique", "special", "below zero", "elsa"],
+ "keywords": [
+ "christmas",
+ "cold",
+ "season",
+ "weather",
+ "winter",
+ "xmas",
+ "snowflake",
+ "snow",
+ "frozen",
+ "droplet",
+ "ice",
+ "crystal",
+ "cold",
+ "chilly",
+ "winter",
+ "unique",
+ "special",
+ "below zero",
+ "elsa"
+ ],
"moji": "❄"
},
"snowman": {
"unicode": "26C4",
- "unicode_alternates": ["26C4-FE0F"],
+ "unicode_alternates": [
+ "26C4-FE0F"
+ ],
"name": "snowman without snow",
"shortname": ":snowman:",
"category": "nature",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["christmas", "cold", "season", "weather", "winter", "xmas"],
+ "keywords": [
+ "christmas",
+ "cold",
+ "season",
+ "weather",
+ "winter",
+ "xmas"
+ ],
"moji": "⛄"
},
+ "snowman2": {
+ "unicode": "2603",
+ "unicode_alternates": "",
+ "name": "snowman",
+ "shortname": ":snowman2:",
+ "category": "nature",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": [
+ "cold",
+ "nature",
+ "snow",
+ "weather"
+ ]
+ },
"sob": {
"unicode": "1F62D",
"unicode_alternates": [],
@@ -11355,18 +25018,41 @@
"category": "emoticons",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["cry", "face", "sad", "tears", "upset", "cry", "sob", "tears", "sad", "melancholy", "morn", "somber", "hurt"],
+ "keywords": [
+ "cry",
+ "face",
+ "sad",
+ "tears",
+ "upset",
+ "cry",
+ "sob",
+ "tears",
+ "sad",
+ "melancholy",
+ "morn",
+ "somber",
+ "hurt"
+ ],
"moji": "😭"
},
"soccer": {
"unicode": "26BD",
- "unicode_alternates": ["26BD-FE0F"],
+ "unicode_alternates": [
+ "26BD-FE0F"
+ ],
"name": "soccer ball",
"shortname": ":soccer:",
"category": "objects",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["balls", "fifa", "football", "sports", "european", "football"],
+ "keywords": [
+ "balls",
+ "fifa",
+ "football",
+ "sports",
+ "european",
+ "football"
+ ],
"moji": "⚽"
},
"soon": {
@@ -11377,7 +25063,10 @@
"category": "other",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["arrow", "words"],
+ "keywords": [
+ "arrow",
+ "words"
+ ],
"moji": "🔜"
},
"sos": {
@@ -11388,7 +25077,12 @@
"category": "other",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["emergency", "help", "red-square", "words"],
+ "keywords": [
+ "emergency",
+ "help",
+ "red-square",
+ "words"
+ ],
"moji": "🆘"
},
"sound": {
@@ -11399,7 +25093,10 @@
"category": "objects",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["speaker", "volume"],
+ "keywords": [
+ "speaker",
+ "volume"
+ ],
"moji": "🔉"
},
"space_invader": {
@@ -11410,18 +25107,26 @@
"category": "objects",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["arcade", "game"],
+ "keywords": [
+ "arcade",
+ "game"
+ ],
"moji": "👾"
},
"spades": {
"unicode": "2660",
- "unicode_alternates": ["2660-FE0F"],
+ "unicode_alternates": [
+ "2660-FE0F"
+ ],
"name": "black spade suit",
"shortname": ":spades:",
"category": "other",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["cards", "poker"],
+ "keywords": [
+ "cards",
+ "poker"
+ ],
"moji": "♠"
},
"spaghetti": {
@@ -11432,18 +25137,32 @@
"category": "objects",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["food", "italian", "noodle", "spaghetti", "noodles", "tomato", "sauce", "italian"],
+ "keywords": [
+ "food",
+ "italian",
+ "noodle",
+ "spaghetti",
+ "noodles",
+ "tomato",
+ "sauce",
+ "italian"
+ ],
"moji": "🍝"
},
"sparkle": {
"unicode": "2747",
- "unicode_alternates": ["2747-FE0F"],
+ "unicode_alternates": [
+ "2747-FE0F"
+ ],
"name": "sparkle",
"shortname": ":sparkle:",
"category": "other",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["green-square", "stars"],
+ "keywords": [
+ "green-square",
+ "stars"
+ ],
"moji": "❇"
},
"sparkler": {
@@ -11454,7 +25173,11 @@
"category": "objects",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["night", "shine", "stars"],
+ "keywords": [
+ "night",
+ "shine",
+ "stars"
+ ],
"moji": "🎇"
},
"sparkles": {
@@ -11465,7 +25188,12 @@
"category": "emoticons",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["cool", "shine", "shiny", "stars"],
+ "keywords": [
+ "cool",
+ "shine",
+ "shiny",
+ "stars"
+ ],
"moji": "✨"
},
"sparkling_heart": {
@@ -11476,7 +25204,12 @@
"category": "emoticons",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["affection", "like", "love", "valentines"],
+ "keywords": [
+ "affection",
+ "like",
+ "love",
+ "valentines"
+ ],
"moji": "💖"
},
"speak_no_evil": {
@@ -11487,7 +25220,19 @@
"category": "emoticons",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["animal", "monkey", "monkey", "mouth", "talk", "say", "words", "verbal", "verbalize", "oral", "iwazaru"],
+ "keywords": [
+ "animal",
+ "monkey",
+ "monkey",
+ "mouth",
+ "talk",
+ "say",
+ "words",
+ "verbal",
+ "verbalize",
+ "oral",
+ "iwazaru"
+ ],
"moji": "🙊"
},
"speaker": {
@@ -11498,7 +25243,12 @@
"category": "objects",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["sound", "listen", "hear", "noise"]
+ "keywords": [
+ "sound",
+ "listen",
+ "hear",
+ "noise"
+ ]
},
"speaking_head": {
"unicode": "1F5E3",
@@ -11506,9 +25256,13 @@
"name": "speaking head in silhouette",
"shortname": ":speaking_head:",
"category": "objects_symbols",
- "aliases": [":speaking_head_in_silhouette:"],
+ "aliases": [
+ ":speaking_head_in_silhouette:"
+ ],
"aliases_ascii": [],
- "keywords": ["talk"]
+ "keywords": [
+ "talk"
+ ]
},
"speech_balloon": {
"unicode": "1F4AC",
@@ -11518,7 +25272,17 @@
"category": "emoticons",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["bubble", "words", "speech", "balloon", "talk", "conversation", "communication", "comic", "dialogue"],
+ "keywords": [
+ "bubble",
+ "words",
+ "speech",
+ "balloon",
+ "talk",
+ "conversation",
+ "communication",
+ "comic",
+ "dialogue"
+ ],
"moji": "💬"
},
"speech_left": {
@@ -11527,9 +25291,19 @@
"name": "left speech bubble",
"shortname": ":speech_left:",
"category": "objects_symbols",
- "aliases": [":left_speech_bubble:"],
- "aliases_ascii": [],
- "keywords": ["balloon", "words", "talk", "conversation", "communication", "comic", "dialogue"]
+ "aliases": [
+ ":left_speech_bubble:"
+ ],
+ "aliases_ascii": [],
+ "keywords": [
+ "balloon",
+ "words",
+ "talk",
+ "conversation",
+ "communication",
+ "comic",
+ "dialogue"
+ ]
},
"speech_right": {
"unicode": "1F5E9",
@@ -11537,9 +25311,19 @@
"name": "right speech bubble",
"shortname": ":speech_right:",
"category": "objects_symbols",
- "aliases": [":right_speech_bubble:"],
- "aliases_ascii": [],
- "keywords": ["balloon", "words", "talk", "conversation", "communication", "comic", "dialogue"]
+ "aliases": [
+ ":right_speech_bubble:"
+ ],
+ "aliases_ascii": [],
+ "keywords": [
+ "balloon",
+ "words",
+ "talk",
+ "conversation",
+ "communication",
+ "comic",
+ "dialogue"
+ ]
},
"speech_three": {
"unicode": "1F5EB",
@@ -11547,9 +25331,19 @@
"name": "three speech bubbles",
"shortname": ":speech_three:",
"category": "objects_symbols",
- "aliases": [":three_speech_bubbles:"],
- "aliases_ascii": [],
- "keywords": ["balloon", "words", "talk", "conversation", "communication", "comic", "dialogue"]
+ "aliases": [
+ ":three_speech_bubbles:"
+ ],
+ "aliases_ascii": [],
+ "keywords": [
+ "balloon",
+ "words",
+ "talk",
+ "conversation",
+ "communication",
+ "comic",
+ "dialogue"
+ ]
},
"speech_two": {
"unicode": "1F5EA",
@@ -11557,9 +25351,19 @@
"name": "two speech bubbles",
"shortname": ":speech_two:",
"category": "objects_symbols",
- "aliases": [":two_speech_bubbles:"],
- "aliases_ascii": [],
- "keywords": ["balloon", "words", "talk", "conversation", "communication", "comic", "dialogue"]
+ "aliases": [
+ ":two_speech_bubbles:"
+ ],
+ "aliases_ascii": [],
+ "keywords": [
+ "balloon",
+ "words",
+ "talk",
+ "conversation",
+ "communication",
+ "comic",
+ "dialogue"
+ ]
},
"speedboat": {
"unicode": "1F6A4",
@@ -11569,7 +25373,16 @@
"category": "places",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["ship", "transportation", "vehicle", "motor", "speed", "ski", "power", "boat"],
+ "keywords": [
+ "ship",
+ "transportation",
+ "vehicle",
+ "motor",
+ "speed",
+ "ski",
+ "power",
+ "boat"
+ ],
"moji": "🚤"
},
"spider": {
@@ -11580,7 +25393,10 @@
"category": "nature",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["arachnid", "eight-legged"]
+ "keywords": [
+ "arachnid",
+ "eight-legged"
+ ]
},
"spider_web": {
"unicode": "1F578",
@@ -11590,7 +25406,9 @@
"category": "nature",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["cobweb"]
+ "keywords": [
+ "cobweb"
+ ]
},
"spy": {
"unicode": "1F575",
@@ -11598,9 +25416,100 @@
"name": "sleuth or spy",
"shortname": ":spy:",
"category": "people",
- "aliases": [":sleuth_or_spy:"],
+ "aliases": [
+ ":sleuth_or_spy:"
+ ],
+ "aliases_ascii": [],
+ "keywords": [
+ "pi",
+ "undercover",
+ "investigator"
+ ]
+ },
+ "spy_tone1": {
+ "unicode": "1F575-1F3FB",
+ "unicode_alternates": "",
+ "name": "sleuth or spy tone 1",
+ "shortname": ":spy_tone1:",
+ "category": "people",
+ "aliases": [
+ ":sleuth_or_spy_tone1:"
+ ],
+ "aliases_ascii": [],
+ "keywords": [
+ "pi",
+ "undercover",
+ "investigator",
+ "person"
+ ]
+ },
+ "spy_tone2": {
+ "unicode": "1F575-1F3FC",
+ "unicode_alternates": "",
+ "name": "sleuth or spy tone 2",
+ "shortname": ":spy_tone2:",
+ "category": "people",
+ "aliases": [
+ ":sleuth_or_spy_tone2:"
+ ],
+ "aliases_ascii": [],
+ "keywords": [
+ "pi",
+ "undercover",
+ "investigator",
+ "person"
+ ]
+ },
+ "spy_tone3": {
+ "unicode": "1F575-1F3FD",
+ "unicode_alternates": "",
+ "name": "sleuth or spy tone 3",
+ "shortname": ":spy_tone3:",
+ "category": "people",
+ "aliases": [
+ ":sleuth_or_spy_tone3:"
+ ],
+ "aliases_ascii": [],
+ "keywords": [
+ "pi",
+ "undercover",
+ "investigator",
+ "person"
+ ]
+ },
+ "spy_tone4": {
+ "unicode": "1F575-1F3FE",
+ "unicode_alternates": "",
+ "name": "sleuth or spy tone 4",
+ "shortname": ":spy_tone4:",
+ "category": "people",
+ "aliases": [
+ ":sleuth_or_spy_tone4:"
+ ],
+ "aliases_ascii": [],
+ "keywords": [
+ "pi",
+ "undercover",
+ "investigator",
+ "person"
+ ]
+ },
+ "spy_tone5": {
+ "unicode": "1F575-1F3FF",
+ "unicode_alternates": "",
+ "name": "sleuth or spy tone 5",
+ "shortname": ":spy_tone5:",
+ "category": "people",
+ "aliases": [
+ ":sleuth_or_spy_tone5:"
+ ],
"aliases_ascii": [],
- "keywords": ["pi", "undercover", "investigator"]
+ "keywords": [
+ "pi",
+ "undercover",
+ "investigator",
+ "person"
+ ]
},
"stadium": {
"unicode": "1F3DF",
@@ -11610,17 +25519,28 @@
"category": "travel_places",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["sport", "event", "concert", "convention", "game"]
+ "keywords": [
+ "sport",
+ "event",
+ "concert",
+ "convention",
+ "game"
+ ]
},
"star": {
"unicode": "2B50",
- "unicode_alternates": ["2B50-FE0F"],
+ "unicode_alternates": [
+ "2B50-FE0F"
+ ],
"name": "white medium star",
"shortname": ":star:",
"category": "nature",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["night", "yellow"],
+ "keywords": [
+ "night",
+ "yellow"
+ ],
"moji": "⭐"
},
"star2": {
@@ -11631,9 +25551,48 @@
"category": "emoticons",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["night", "sparkle", "glow", "glowing", "star", "five", "points", "classic"],
+ "keywords": [
+ "night",
+ "sparkle",
+ "glow",
+ "glowing",
+ "star",
+ "five",
+ "points",
+ "classic"
+ ],
"moji": "🌟"
},
+ "star_and_crescent": {
+ "unicode": "262A",
+ "unicode_alternates": "",
+ "name": "star and crescent",
+ "shortname": ":star_and_crescent:",
+ "category": "symbols",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": [
+ "islam",
+ "muslim",
+ "religion",
+ "symbol"
+ ]
+ },
+ "star_of_david": {
+ "unicode": "2721",
+ "unicode_alternates": "",
+ "name": "star of david",
+ "shortname": ":star_of_david:",
+ "category": "symbols",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": [
+ "jew",
+ "jewish",
+ "religion",
+ "symbol"
+ ]
+ },
"stars": {
"unicode": "1F320",
"unicode_alternates": [],
@@ -11642,7 +25601,17 @@
"category": "nature",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["night", "photo", "shooting", "shoot", "star", "sky", "night", "comet", "meteoroid"],
+ "keywords": [
+ "night",
+ "photo",
+ "shooting",
+ "shoot",
+ "star",
+ "sky",
+ "night",
+ "comet",
+ "meteoroid"
+ ],
"moji": "🌠"
},
"station": {
@@ -11653,7 +25622,14 @@
"category": "places",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["public", "transportation", "vehicle", "station", "train", "subway"],
+ "keywords": [
+ "public",
+ "transportation",
+ "vehicle",
+ "station",
+ "train",
+ "subway"
+ ],
"moji": "🚉"
},
"statue_of_liberty": {
@@ -11664,7 +25640,10 @@
"category": "places",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["american", "newyork"],
+ "keywords": [
+ "american",
+ "newyork"
+ ],
"moji": "🗽"
},
"steam_locomotive": {
@@ -11675,7 +25654,15 @@
"category": "places",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["train", "transportation", "vehicle", "locomotive", "steam", "train", "engine"],
+ "keywords": [
+ "train",
+ "transportation",
+ "vehicle",
+ "locomotive",
+ "steam",
+ "train",
+ "engine"
+ ],
"moji": "🚂"
},
"stereo": {
@@ -11684,9 +25671,17 @@
"name": "portable stereo",
"shortname": ":stereo:",
"category": "objects_symbols",
- "aliases": [":portable_stereo:"],
- "aliases_ascii": [],
- "keywords": ["communication", "music", "program", "boom", "box"]
+ "aliases": [
+ ":portable_stereo:"
+ ],
+ "aliases_ascii": [],
+ "keywords": [
+ "communication",
+ "music",
+ "program",
+ "boom",
+ "box"
+ ]
},
"stew": {
"unicode": "1F372",
@@ -11696,7 +25691,16 @@
"category": "objects",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["food", "meat", "stew", "hearty", "soup", "thick", "hot", "pot"],
+ "keywords": [
+ "food",
+ "meat",
+ "stew",
+ "hearty",
+ "soup",
+ "thick",
+ "hot",
+ "pot"
+ ],
"moji": "🍲"
},
"stock_chart": {
@@ -11707,7 +25711,39 @@
"category": "objects_symbols",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["graph", "presentation", "stats", "business"]
+ "keywords": [
+ "graph",
+ "presentation",
+ "stats",
+ "business"
+ ]
+ },
+ "stop_button": {
+ "unicode": "23F9",
+ "unicode_alternates": "",
+ "name": "black square for stop",
+ "shortname": ":stop_button:",
+ "category": "symbols",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": [
+ "sound",
+ "symbol"
+ ]
+ },
+ "stopwatch": {
+ "unicode": "23F1",
+ "unicode_alternates": "",
+ "name": "stopwatch",
+ "shortname": ":stopwatch:",
+ "category": "objects",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": [
+ "clock",
+ "object",
+ "time"
+ ]
},
"straight_ruler": {
"unicode": "1F4CF",
@@ -11717,7 +25753,9 @@
"category": "objects",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["stationery"],
+ "keywords": [
+ "stationery"
+ ],
"moji": "📏"
},
"strawberry": {
@@ -11728,7 +25766,15 @@
"category": "objects",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["food", "fruit", "nature", "strawberry", "short", "cake", "berry"],
+ "keywords": [
+ "food",
+ "fruit",
+ "nature",
+ "strawberry",
+ "short",
+ "cake",
+ "berry"
+ ],
"moji": "🍓"
},
"stuck_out_tongue": {
@@ -11738,8 +25784,32 @@
"shortname": ":stuck_out_tongue:",
"category": "emoticons",
"aliases": [],
- "aliases_ascii": [":P", ":-P", "=P", ":-p", ":p", "=p", ":-Þ", ":Þ", ":þ", ":-þ", ":-b", ":b", "d:"],
- "keywords": ["childish", "face", "mischievous", "playful", "prank", "tongue", "silly", "playful", "cheeky"],
+ "aliases_ascii": [
+ ":P",
+ ":-P",
+ "=P",
+ ":-p",
+ ":p",
+ "=p",
+ ":-Þ",
+ ":Þ",
+ ":þ",
+ ":-þ",
+ ":-b",
+ ":b",
+ "d:"
+ ],
+ "keywords": [
+ "childish",
+ "face",
+ "mischievous",
+ "playful",
+ "prank",
+ "tongue",
+ "silly",
+ "playful",
+ "cheeky"
+ ],
"moji": "😛"
},
"stuck_out_tongue_closed_eyes": {
@@ -11750,7 +25820,17 @@
"category": "emoticons",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["face", "mischievous", "playful", "prank", "tongue", "kidding", "silly", "playful", "ecstatic"],
+ "keywords": [
+ "face",
+ "mischievous",
+ "playful",
+ "prank",
+ "tongue",
+ "kidding",
+ "silly",
+ "playful",
+ "ecstatic"
+ ],
"moji": "😝"
},
"stuck_out_tongue_winking_eye": {
@@ -11760,8 +25840,25 @@
"shortname": ":stuck_out_tongue_winking_eye:",
"category": "emoticons",
"aliases": [],
- "aliases_ascii": [">:P", "X-P", "x-p"],
- "keywords": ["childish", "face", "mischievous", "playful", "prank", "tongue", "wink", "winking", "kidding", "silly", "playful", "crazy"],
+ "aliases_ascii": [
+ ">:P",
+ "X-P",
+ "x-p"
+ ],
+ "keywords": [
+ "childish",
+ "face",
+ "mischievous",
+ "playful",
+ "prank",
+ "tongue",
+ "wink",
+ "winking",
+ "kidding",
+ "silly",
+ "playful",
+ "crazy"
+ ],
"moji": "😜"
},
"sun_with_face": {
@@ -11772,7 +25869,13 @@
"category": "nature",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["morning", "sun", "anthropomorphic", "face", "sky"],
+ "keywords": [
+ "morning",
+ "sun",
+ "anthropomorphic",
+ "face",
+ "sky"
+ ],
"moji": "🌞"
},
"sunflower": {
@@ -11783,7 +25886,15 @@
"category": "nature",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["nature", "plant", "sunflower", "sun", "flower", "seeds", "yellow"],
+ "keywords": [
+ "nature",
+ "plant",
+ "sunflower",
+ "sun",
+ "flower",
+ "seeds",
+ "yellow"
+ ],
"moji": "🌻"
},
"sunglasses": {
@@ -11793,19 +25904,41 @@
"shortname": ":sunglasses:",
"category": "emoticons",
"aliases": [],
- "aliases_ascii": ["B-)", "B)", "8)", "8-)", "B-D", "8-D"],
- "keywords": ["cool", "face", "smiling", "sunglasses", "sun", "glasses", "sunny", "cool", "smooth"],
+ "aliases_ascii": [
+ "B-)",
+ "B)",
+ "8)",
+ "8-)",
+ "B-D",
+ "8-D"
+ ],
+ "keywords": [
+ "cool",
+ "face",
+ "smiling",
+ "sunglasses",
+ "sun",
+ "glasses",
+ "sunny",
+ "cool",
+ "smooth"
+ ],
"moji": "😎"
},
"sunny": {
"unicode": "2600",
- "unicode_alternates": ["2600-FE0F"],
+ "unicode_alternates": [
+ "2600-FE0F"
+ ],
"name": "black sun with rays",
"shortname": ":sunny:",
"category": "nature",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["brightness", "weather"]
+ "keywords": [
+ "brightness",
+ "weather"
+ ]
},
"sunrise": {
"unicode": "1F305",
@@ -11815,7 +25948,17 @@
"category": "places",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["morning", "photo", "vacation", "view", "sunrise", "sun", "morning", "color", "sky"],
+ "keywords": [
+ "morning",
+ "photo",
+ "vacation",
+ "view",
+ "sunrise",
+ "sun",
+ "morning",
+ "color",
+ "sky"
+ ],
"moji": "🌅"
},
"sunrise_over_mountains": {
@@ -11826,7 +25969,18 @@
"category": "places",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["photo", "vacation", "view", "sunrise", "sun", "morning", "mountain", "rural", "color", "sky"],
+ "keywords": [
+ "photo",
+ "vacation",
+ "view",
+ "sunrise",
+ "sun",
+ "morning",
+ "mountain",
+ "rural",
+ "color",
+ "sky"
+ ],
"moji": "🌄"
},
"surfer": {
@@ -11837,9 +25991,114 @@
"category": "objects",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["ocean", "sea", "sports", "surfer", "surf", "wave", "ocean", "ride", "swell"],
+ "keywords": [
+ "ocean",
+ "sea",
+ "sports",
+ "surfer",
+ "surf",
+ "wave",
+ "ocean",
+ "ride",
+ "swell"
+ ],
"moji": "🏄"
},
+ "surfer_tone1": {
+ "unicode": "1F3C4-1F3FB",
+ "unicode_alternates": "",
+ "name": "surfer tone 1",
+ "shortname": ":surfer_tone1:",
+ "category": "activity",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": [
+ "ocean",
+ "sea",
+ "sport",
+ "surf",
+ "wave",
+ "ocean",
+ "ride",
+ "swell"
+ ]
+ },
+ "surfer_tone2": {
+ "unicode": "1F3C4-1F3FC",
+ "unicode_alternates": "",
+ "name": "surfer tone 2",
+ "shortname": ":surfer_tone2:",
+ "category": "activity",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": [
+ "ocean",
+ "sea",
+ "sport",
+ "surf",
+ "wave",
+ "ocean",
+ "ride",
+ "swell"
+ ]
+ },
+ "surfer_tone3": {
+ "unicode": "1F3C4-1F3FD",
+ "unicode_alternates": "",
+ "name": "surfer tone 3",
+ "shortname": ":surfer_tone3:",
+ "category": "activity",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": [
+ "ocean",
+ "sea",
+ "sport",
+ "surf",
+ "wave",
+ "ocean",
+ "ride",
+ "swell"
+ ]
+ },
+ "surfer_tone4": {
+ "unicode": "1F3C4-1F3FE",
+ "unicode_alternates": "",
+ "name": "surfer tone 4",
+ "shortname": ":surfer_tone4:",
+ "category": "activity",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": [
+ "ocean",
+ "sea",
+ "sport",
+ "surf",
+ "wave",
+ "ocean",
+ "ride",
+ "swell"
+ ]
+ },
+ "surfer_tone5": {
+ "unicode": "1F3C4-1F3FF",
+ "unicode_alternates": "",
+ "name": "surfer tone 5",
+ "shortname": ":surfer_tone5:",
+ "category": "activity",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": [
+ "ocean",
+ "sea",
+ "sport",
+ "surf",
+ "wave",
+ "ocean",
+ "ride",
+ "swell"
+ ]
+ },
"sushi": {
"unicode": "1F363",
"unicode_alternates": [],
@@ -11848,7 +26107,15 @@
"category": "objects",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["food", "japanese", "sushi", "fish", "raw", "nigiri", "japanese"],
+ "keywords": [
+ "food",
+ "japanese",
+ "sushi",
+ "fish",
+ "raw",
+ "nigiri",
+ "japanese"
+ ],
"moji": "🍣"
},
"suspension_railway": {
@@ -11859,7 +26126,15 @@
"category": "places",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["transportation", "vehicle", "suspension", "railway", "rail", "train", "transportation"],
+ "keywords": [
+ "transportation",
+ "vehicle",
+ "suspension",
+ "railway",
+ "rail",
+ "train",
+ "transportation"
+ ],
"moji": "🚟"
},
"sweat": {
@@ -11869,8 +26144,22 @@
"shortname": ":sweat:",
"category": "emoticons",
"aliases": [],
- "aliases_ascii": ["':(", "':-(", "'=("],
- "keywords": ["cold", "sweat", "sick", "anxious", "worried", "clammy", "diaphoresis", "face", "hot"],
+ "aliases_ascii": [
+ "':(",
+ "':-(",
+ "'=("
+ ],
+ "keywords": [
+ "cold",
+ "sweat",
+ "sick",
+ "anxious",
+ "worried",
+ "clammy",
+ "diaphoresis",
+ "face",
+ "hot"
+ ],
"moji": "😓"
},
"sweat_drops": {
@@ -11881,7 +26170,9 @@
"category": "emoticons",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["water"],
+ "keywords": [
+ "water"
+ ],
"moji": "💦"
},
"sweat_smile": {
@@ -11891,8 +26182,23 @@
"shortname": ":sweat_smile:",
"category": "emoticons",
"aliases": [],
- "aliases_ascii": ["':)", "':-)", "'=)", "':D", "':-D", "'=D"],
- "keywords": ["face", "happy", "hot", "smiling", "cold", "sweat", "perspiration"],
+ "aliases_ascii": [
+ "':)",
+ "':-)",
+ "'=)",
+ "':D",
+ "':-D",
+ "'=D"
+ ],
+ "keywords": [
+ "face",
+ "happy",
+ "hot",
+ "smiling",
+ "cold",
+ "sweat",
+ "perspiration"
+ ],
"moji": "😅"
},
"sweet_potato": {
@@ -11903,7 +26209,15 @@
"category": "objects",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["food", "nature", "sweet", "potato", "potassium", "roasted", "roast"],
+ "keywords": [
+ "food",
+ "nature",
+ "sweet",
+ "potato",
+ "potassium",
+ "roasted",
+ "roast"
+ ],
"moji": "🍠"
},
"swimmer": {
@@ -11914,9 +26228,120 @@
"category": "objects",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["sports", "swimmer", "swim", "water", "pool", "laps", "freestyle", "butterfly", "breaststroke", "backstroke"],
+ "keywords": [
+ "sports",
+ "swimmer",
+ "swim",
+ "water",
+ "pool",
+ "laps",
+ "freestyle",
+ "butterfly",
+ "breaststroke",
+ "backstroke"
+ ],
"moji": "🏊"
},
+ "swimmer_tone1": {
+ "unicode": "1F3CA-1F3FB",
+ "unicode_alternates": "",
+ "name": "swimmer tone 1",
+ "shortname": ":swimmer_tone1:",
+ "category": "activity",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": [
+ "sport",
+ "swim",
+ "water",
+ "pool",
+ "laps",
+ "freestyle",
+ "butterfly",
+ "breaststroke",
+ "backstroke"
+ ]
+ },
+ "swimmer_tone2": {
+ "unicode": "1F3CA-1F3FC",
+ "unicode_alternates": "",
+ "name": "swimmer tone 2",
+ "shortname": ":swimmer_tone2:",
+ "category": "activity",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": [
+ "sport",
+ "swim",
+ "water",
+ "pool",
+ "laps",
+ "freestyle",
+ "butterfly",
+ "breaststroke",
+ "backstroke"
+ ]
+ },
+ "swimmer_tone3": {
+ "unicode": "1F3CA-1F3FD",
+ "unicode_alternates": "",
+ "name": "swimmer tone 3",
+ "shortname": ":swimmer_tone3:",
+ "category": "activity",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": [
+ "sport",
+ "swim",
+ "water",
+ "pool",
+ "laps",
+ "freestyle",
+ "butterfly",
+ "breaststroke",
+ "backstroke"
+ ]
+ },
+ "swimmer_tone4": {
+ "unicode": "1F3CA-1F3FE",
+ "unicode_alternates": "",
+ "name": "swimmer tone 4",
+ "shortname": ":swimmer_tone4:",
+ "category": "activity",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": [
+ "sport",
+ "swim",
+ "water",
+ "pool",
+ "laps",
+ "freestyle",
+ "butterfly",
+ "breaststroke",
+ "backstroke"
+ ]
+ },
+ "swimmer_tone5": {
+ "unicode": "1F3CA-1F3FF",
+ "unicode_alternates": "",
+ "name": "swimmer tone 5",
+ "shortname": ":swimmer_tone5:",
+ "category": "activity",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": [
+ "sport",
+ "swim",
+ "water",
+ "pool",
+ "laps",
+ "freestyle",
+ "butterfly",
+ "breaststroke",
+ "backstroke"
+ ]
+ },
"symbols": {
"unicode": "1F523",
"unicode_alternates": [],
@@ -11925,9 +26350,21 @@
"category": "other",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["blue-square"],
+ "keywords": [
+ "blue-square"
+ ],
"moji": "🔣"
},
+ "synagogue": {
+ "unicode": "1F54D",
+ "unicode_alternates": "",
+ "name": "synagogue",
+ "shortname": ":synagogue:",
+ "category": "travel",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": []
+ },
"syringe": {
"unicode": "1F489",
"unicode_alternates": [],
@@ -11936,9 +26373,26 @@
"category": "objects",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["blood", "drugs", "health", "hospital", "medicine", "needle"],
+ "keywords": [
+ "blood",
+ "drugs",
+ "health",
+ "hospital",
+ "medicine",
+ "needle"
+ ],
"moji": "💉"
},
+ "taco": {
+ "unicode": "1F32E",
+ "unicode_alternates": "",
+ "name": "taco",
+ "shortname": ":taco:",
+ "category": "foods",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": []
+ },
"tada": {
"unicode": "1F389",
"unicode_alternates": [],
@@ -11947,7 +26401,18 @@
"category": "objects",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["contulations", "party", "party", "popper", "tada", "celebration", "victory", "announcement", "climax", "congratulations"],
+ "keywords": [
+ "contulations",
+ "party",
+ "party",
+ "popper",
+ "tada",
+ "celebration",
+ "victory",
+ "announcement",
+ "climax",
+ "congratulations"
+ ],
"moji": "🎉"
},
"tanabata_tree": {
@@ -11958,7 +26423,16 @@
"category": "objects",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["nature", "plant", "tanabata", "tree", "festival", "star", "wish", "holiday"],
+ "keywords": [
+ "nature",
+ "plant",
+ "tanabata",
+ "tree",
+ "festival",
+ "star",
+ "wish",
+ "holiday"
+ ],
"moji": "🎋"
},
"tangerine": {
@@ -11969,18 +26443,40 @@
"category": "objects",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["food", "fruit", "nature", "tangerine", "citrus", "orange"],
+ "keywords": [
+ "food",
+ "fruit",
+ "nature",
+ "tangerine",
+ "citrus",
+ "orange"
+ ],
"moji": "🍊"
},
"taurus": {
"unicode": "2649",
- "unicode_alternates": ["2649-FE0F"],
+ "unicode_alternates": [
+ "2649-FE0F"
+ ],
"name": "taurus",
"shortname": ":taurus:",
"category": "other",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["purple-square", "sign", "taurus", "bull", "astrology", "greek", "constellation", "stars", "zodiac", "sign", "zodiac", "horoscope"],
+ "keywords": [
+ "purple-square",
+ "sign",
+ "taurus",
+ "bull",
+ "astrology",
+ "greek",
+ "constellation",
+ "stars",
+ "zodiac",
+ "sign",
+ "zodiac",
+ "horoscope"
+ ],
"moji": "♉"
},
"taxi": {
@@ -11991,7 +26487,18 @@
"category": "places",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["cars", "transportation", "uber", "vehicle", "taxi", "car", "automobile", "city", "transport", "service"],
+ "keywords": [
+ "cars",
+ "transportation",
+ "uber",
+ "vehicle",
+ "taxi",
+ "car",
+ "automobile",
+ "city",
+ "transport",
+ "service"
+ ],
"moji": "🚕"
},
"tea": {
@@ -12002,18 +26509,36 @@
"category": "objects",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["bowl", "breakfast", "british", "drink", "green", "tea", "leaf", "drink", "teacup", "hot", "beverage"],
+ "keywords": [
+ "bowl",
+ "breakfast",
+ "british",
+ "drink",
+ "green",
+ "tea",
+ "leaf",
+ "drink",
+ "teacup",
+ "hot",
+ "beverage"
+ ],
"moji": "🍵"
},
"telephone": {
"unicode": "260E",
- "unicode_alternates": ["260E-FE0F"],
+ "unicode_alternates": [
+ "260E-FE0F"
+ ],
"name": "black telephone",
"shortname": ":telephone:",
"category": "objects",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["communication", "dial", "technology"],
+ "keywords": [
+ "communication",
+ "dial",
+ "technology"
+ ],
"moji": "☎"
},
"telephone_black": {
@@ -12022,9 +26547,15 @@
"name": "black touchtone telephone",
"shortname": ":telephone_black:",
"category": "objects_symbols",
- "aliases": [":black_touchtone_telephone:"],
+ "aliases": [
+ ":black_touchtone_telephone:"
+ ],
"aliases_ascii": [],
- "keywords": ["communication", "dial", "technology"]
+ "keywords": [
+ "communication",
+ "dial",
+ "technology"
+ ]
},
"telephone_receiver": {
"unicode": "1F4DE",
@@ -12034,7 +26565,11 @@
"category": "objects",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["communication", "dial", "technology"],
+ "keywords": [
+ "communication",
+ "dial",
+ "technology"
+ ],
"moji": "📞"
},
"telephone_white": {
@@ -12043,9 +26578,15 @@
"name": "white touchtone telephone",
"shortname": ":telephone_white:",
"category": "objects_symbols",
- "aliases": [":white_touchtone_telephone:"],
+ "aliases": [
+ ":white_touchtone_telephone:"
+ ],
"aliases_ascii": [],
- "keywords": ["communication", "dial", "technology"]
+ "keywords": [
+ "communication",
+ "dial",
+ "technology"
+ ]
},
"telescope": {
"unicode": "1F52D",
@@ -12055,9 +26596,28 @@
"category": "objects",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["space", "stars"],
+ "keywords": [
+ "space",
+ "stars"
+ ],
"moji": "🔭"
},
+ "ten": {
+ "unicode": "1F51F",
+ "unicode_alternates": "",
+ "name": "keycap ten",
+ "shortname": ":ten:",
+ "category": "symbols",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": [
+ "10",
+ "blue-square",
+ "numbers",
+ "symbol",
+ "word"
+ ]
+ },
"tennis": {
"unicode": "1F3BE",
"unicode_alternates": [],
@@ -12066,18 +26626,36 @@
"category": "objects",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["balls", "green", "sports", "tennis", "racket", "racquet", "ball", "game", "net", "court", "love"],
+ "keywords": [
+ "balls",
+ "green",
+ "sports",
+ "tennis",
+ "racket",
+ "racquet",
+ "ball",
+ "game",
+ "net",
+ "court",
+ "love"
+ ],
"moji": "🎾"
},
"tent": {
"unicode": "26FA",
- "unicode_alternates": ["26FA-FE0F"],
+ "unicode_alternates": [
+ "26FA-FE0F"
+ ],
"name": "tent",
"shortname": ":tent:",
"category": "places",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["camp", "outdoors", "photo"],
+ "keywords": [
+ "camp",
+ "outdoors",
+ "photo"
+ ],
"moji": "⛺"
},
"thermometer": {
@@ -12088,7 +26666,33 @@
"category": "objects_symbols",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["temperature"]
+ "keywords": [
+ "temperature"
+ ]
+ },
+ "thermometer_face": {
+ "unicode": "1F912",
+ "unicode_alternates": "",
+ "name": "face with thermometer",
+ "shortname": ":thermometer_face:",
+ "category": "people",
+ "aliases": [
+ ":face_with_thermometer:"
+ ],
+ "aliases_ascii": [],
+ "keywords": []
+ },
+ "thinking": {
+ "unicode": "1F914",
+ "unicode_alternates": "",
+ "name": "thinking face",
+ "shortname": ":thinking:",
+ "category": "people",
+ "aliases": [
+ ":thinking_face:"
+ ],
+ "aliases_ascii": [],
+ "keywords": []
},
"thought_balloon": {
"unicode": "1F4AD",
@@ -12098,7 +26702,17 @@
"category": "emoticons",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["bubble", "cloud", "speech", "thought", "balloon", "comic", "think", "day dream", "wonder"],
+ "keywords": [
+ "bubble",
+ "cloud",
+ "speech",
+ "thought",
+ "balloon",
+ "comic",
+ "think",
+ "day dream",
+ "wonder"
+ ],
"moji": "💭"
},
"thought_left": {
@@ -12107,9 +26721,18 @@
"name": "left thought bubble",
"shortname": ":thought_left:",
"category": "objects_symbols",
- "aliases": [":left_thought_bubble:"],
- "aliases_ascii": [],
- "keywords": ["balloon", "cloud", "comic", "think", "day dream", "wonder"]
+ "aliases": [
+ ":left_thought_bubble:"
+ ],
+ "aliases_ascii": [],
+ "keywords": [
+ "balloon",
+ "cloud",
+ "comic",
+ "think",
+ "day dream",
+ "wonder"
+ ]
},
"thought_right": {
"unicode": "1F5ED",
@@ -12117,20 +26740,36 @@
"name": "right thought bubble",
"shortname": ":thought_right:",
"category": "objects_symbols",
- "aliases": [":right_thought_bubble:"],
- "aliases_ascii": [],
- "keywords": ["balloon", "cloud", "comic", "think", "day dream", "wonder"]
+ "aliases": [
+ ":right_thought_bubble:"
+ ],
+ "aliases_ascii": [],
+ "keywords": [
+ "balloon",
+ "cloud",
+ "comic",
+ "think",
+ "day dream",
+ "wonder"
+ ]
},
"three": {
"moji": "3️⃣",
"unicode": "0033-20E3",
- "unicode_alternates": ["0033-FE0F-20E3"],
+ "unicode_alternates": [
+ "0033-FE0F-20E3"
+ ],
"name": "digit three",
"shortname": ":three:",
"category": "other",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["3", "blue-square", "numbers", "prime"]
+ "keywords": [
+ "3",
+ "blue-square",
+ "numbers",
+ "prime"
+ ]
},
"thumbs_down_reverse": {
"unicode": "1F593",
@@ -12138,9 +26777,15 @@
"name": "reversed thumbs down sign",
"shortname": ":thumbs_down_reverse:",
"category": "people",
- "aliases": [":reversed_thumbs_down_sign:"],
+ "aliases": [
+ ":reversed_thumbs_down_sign:"
+ ],
"aliases_ascii": [],
- "keywords": ["hand", "no", "-1"]
+ "keywords": [
+ "hand",
+ "no",
+ "-1"
+ ]
},
"thumbs_up_reverse": {
"unicode": "1F592",
@@ -12148,9 +26793,17 @@
"name": "reversed thumbs up sign",
"shortname": ":thumbs_up_reverse:",
"category": "people",
- "aliases": [":reversed_thumbs_up_sign:"],
- "aliases_ascii": [],
- "keywords": ["cool", "hand", "like", "yes", "+1"]
+ "aliases": [
+ ":reversed_thumbs_up_sign:"
+ ],
+ "aliases_ascii": [],
+ "keywords": [
+ "cool",
+ "hand",
+ "like",
+ "yes",
+ "+1"
+ ]
},
"thumbsdown": {
"unicode": "1F44E",
@@ -12158,22 +26811,219 @@
"name": "thumbs down sign",
"shortname": ":thumbsdown:",
"category": "emoticons",
- "aliases": [":-1:"],
- "aliases_ascii": [],
- "keywords": ["hand", "no"],
+ "aliases": [
+ ":-1:"
+ ],
+ "aliases_ascii": [],
+ "keywords": [
+ "hand",
+ "no"
+ ],
"moji": "👎"
},
+ "thumbsdown_tone1": {
+ "unicode": "1F44E-1F3FB",
+ "unicode_alternates": "",
+ "name": "thumbs down sign tone 1",
+ "shortname": ":thumbsdown_tone1:",
+ "category": "people",
+ "aliases": [
+ ":-1_tone1:"
+ ],
+ "aliases_ascii": [],
+ "keywords": [
+ "hand",
+ "no",
+ "-1"
+ ]
+ },
+ "thumbsdown_tone2": {
+ "unicode": "1F44E-1F3FC",
+ "unicode_alternates": "",
+ "name": "thumbs down sign tone 2",
+ "shortname": ":thumbsdown_tone2:",
+ "category": "people",
+ "aliases": [
+ ":-1_tone2:"
+ ],
+ "aliases_ascii": [],
+ "keywords": [
+ "hand",
+ "no",
+ "-1"
+ ]
+ },
+ "thumbsdown_tone3": {
+ "unicode": "1F44E-1F3FD",
+ "unicode_alternates": "",
+ "name": "thumbs down sign tone 3",
+ "shortname": ":thumbsdown_tone3:",
+ "category": "people",
+ "aliases": [
+ ":-1_tone3:"
+ ],
+ "aliases_ascii": [],
+ "keywords": [
+ "hand",
+ "no",
+ "-1"
+ ]
+ },
+ "thumbsdown_tone4": {
+ "unicode": "1F44E-1F3FE",
+ "unicode_alternates": "",
+ "name": "thumbs down sign tone 4",
+ "shortname": ":thumbsdown_tone4:",
+ "category": "people",
+ "aliases": [
+ ":-1_tone4:"
+ ],
+ "aliases_ascii": [],
+ "keywords": [
+ "hand",
+ "no",
+ "-1"
+ ]
+ },
+ "thumbsdown_tone5": {
+ "unicode": "1F44E-1F3FF",
+ "unicode_alternates": "",
+ "name": "thumbs down sign tone 5",
+ "shortname": ":thumbsdown_tone5:",
+ "category": "people",
+ "aliases": [
+ ":-1_tone5:"
+ ],
+ "aliases_ascii": [],
+ "keywords": [
+ "hand",
+ "no",
+ "-1"
+ ]
+ },
"thumbsup": {
"unicode": "1F44D",
"unicode_alternates": [],
"name": "thumbs up sign",
"shortname": ":thumbsup:",
"category": "emoticons",
- "aliases": [":+1:"],
- "aliases_ascii": [],
- "keywords": ["cool", "hand", "like", "yes"],
+ "aliases": [
+ ":+1:"
+ ],
+ "aliases_ascii": [],
+ "keywords": [
+ "cool",
+ "hand",
+ "like",
+ "yes"
+ ],
"moji": "👍"
},
+ "thumbsup_tone1": {
+ "unicode": "1F44D-1F3FB",
+ "unicode_alternates": "",
+ "name": "thumbs up sign tone 1",
+ "shortname": ":thumbsup_tone1:",
+ "category": "people",
+ "aliases": [
+ ":+1_tone1:"
+ ],
+ "aliases_ascii": [],
+ "keywords": [
+ "cool",
+ "hand",
+ "like",
+ "yes",
+ "+1"
+ ]
+ },
+ "thumbsup_tone2": {
+ "unicode": "1F44D-1F3FC",
+ "unicode_alternates": "",
+ "name": "thumbs up sign tone 2",
+ "shortname": ":thumbsup_tone2:",
+ "category": "people",
+ "aliases": [
+ ":+1_tone2:"
+ ],
+ "aliases_ascii": [],
+ "keywords": [
+ "cool",
+ "hand",
+ "like",
+ "yes",
+ "+1"
+ ]
+ },
+ "thumbsup_tone3": {
+ "unicode": "1F44D-1F3FD",
+ "unicode_alternates": "",
+ "name": "thumbs up sign tone 3",
+ "shortname": ":thumbsup_tone3:",
+ "category": "people",
+ "aliases": [
+ ":+1_tone3:"
+ ],
+ "aliases_ascii": [],
+ "keywords": [
+ "cool",
+ "hand",
+ "like",
+ "yes",
+ "+1"
+ ]
+ },
+ "thumbsup_tone4": {
+ "unicode": "1F44D-1F3FE",
+ "unicode_alternates": "",
+ "name": "thumbs up sign tone 4",
+ "shortname": ":thumbsup_tone4:",
+ "category": "people",
+ "aliases": [
+ ":+1_tone4:"
+ ],
+ "aliases_ascii": [],
+ "keywords": [
+ "cool",
+ "hand",
+ "like",
+ "yes",
+ "+1"
+ ]
+ },
+ "thumbsup_tone5": {
+ "unicode": "1F44D-1F3FF",
+ "unicode_alternates": "",
+ "name": "thumbs up sign tone 5",
+ "shortname": ":thumbsup_tone5:",
+ "category": "people",
+ "aliases": [
+ ":+1_tone5:"
+ ],
+ "aliases_ascii": [],
+ "keywords": [
+ "cool",
+ "hand",
+ "like",
+ "yes",
+ "+1"
+ ]
+ },
+ "thunder_cloud_rain": {
+ "unicode": "26C8",
+ "unicode_alternates": "",
+ "name": "thunder cloud and rain",
+ "shortname": ":thunder_cloud_rain:",
+ "category": "nature",
+ "aliases": [
+ ":thunder_cloud_and_rain:"
+ ],
+ "aliases_ascii": [],
+ "keywords": [
+ "nature",
+ "weather"
+ ]
+ },
"ticket": {
"unicode": "1F3AB",
"unicode_alternates": [],
@@ -12182,7 +27032,18 @@
"category": "places",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["concert", "event", "pass", "ticket", "show", "entertainment", "stub", "admission", "proof", "purchase"],
+ "keywords": [
+ "concert",
+ "event",
+ "pass",
+ "ticket",
+ "show",
+ "entertainment",
+ "stub",
+ "admission",
+ "proof",
+ "purchase"
+ ],
"moji": "🎫"
},
"tickets": {
@@ -12191,9 +27052,20 @@
"name": "admission tickets",
"shortname": ":tickets:",
"category": "activity",
- "aliases": [":admission_tickets:"],
- "aliases_ascii": [],
- "keywords": ["concert", "event", "pass", "show", "entertainment", "stub", "proof", "purchase"]
+ "aliases": [
+ ":admission_tickets:"
+ ],
+ "aliases_ascii": [],
+ "keywords": [
+ "concert",
+ "event",
+ "pass",
+ "show",
+ "entertainment",
+ "stub",
+ "proof",
+ "purchase"
+ ]
},
"tiger": {
"unicode": "1F42F",
@@ -12203,7 +27075,9 @@
"category": "nature",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["animal"],
+ "keywords": [
+ "animal"
+ ],
"moji": "🐯"
},
"tiger2": {
@@ -12214,9 +27088,33 @@
"category": "nature",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["animal", "nature", "tiger", "cat", "striped", "tony", "tigger", "hobs"],
+ "keywords": [
+ "animal",
+ "nature",
+ "tiger",
+ "cat",
+ "striped",
+ "tony",
+ "tigger",
+ "hobs"
+ ],
"moji": "🐅"
},
+ "timer": {
+ "unicode": "23F2",
+ "unicode_alternates": "",
+ "name": "timer clock",
+ "shortname": ":timer:",
+ "category": "objects",
+ "aliases": [
+ ":timer_clock:"
+ ],
+ "aliases_ascii": [],
+ "keywords": [
+ "object",
+ "time"
+ ]
+ },
"tired_face": {
"unicode": "1F62B",
"unicode_alternates": [],
@@ -12225,9 +27123,34 @@
"category": "emoticons",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["face", "frustrated", "sick", "upset", "whine", "exhausted", "sleepy", "tired"],
+ "keywords": [
+ "face",
+ "frustrated",
+ "sick",
+ "upset",
+ "whine",
+ "exhausted",
+ "sleepy",
+ "tired"
+ ],
"moji": "😫"
},
+ "tm": {
+ "unicode": "2122",
+ "unicode_alternates": "2122-fe0f",
+ "name": "trade mark sign",
+ "shortname": ":tm:",
+ "category": "symbols",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": [
+ "brand",
+ "trademark",
+ "symbol",
+ "tm",
+ "word"
+ ]
+ },
"toilet": {
"unicode": "1F6BD",
"unicode_alternates": [],
@@ -12236,7 +27159,17 @@
"category": "objects",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["restroom", "wc", "toilet", "bathroom", "throne", "porcelain", "waste", "flush", "plumbing"],
+ "keywords": [
+ "restroom",
+ "wc",
+ "toilet",
+ "bathroom",
+ "throne",
+ "porcelain",
+ "waste",
+ "flush",
+ "plumbing"
+ ],
"moji": "🚽"
},
"tokyo_tower": {
@@ -12247,7 +27180,10 @@
"category": "places",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["japan", "photo"],
+ "keywords": [
+ "japan",
+ "photo"
+ ],
"moji": "🗼"
},
"tomato": {
@@ -12258,9 +27194,68 @@
"category": "objects",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["food", "fruit", "nature", "vegetable", "tomato", "fruit", "sauce", "italian"],
+ "keywords": [
+ "food",
+ "fruit",
+ "nature",
+ "vegetable",
+ "tomato",
+ "fruit",
+ "sauce",
+ "italian"
+ ],
"moji": "🍅"
},
+ "tone1": {
+ "unicode": "1F3FB",
+ "unicode_alternates": "",
+ "name": "emoji modifier Fitzpatrick type-1-2",
+ "shortname": ":tone1:",
+ "category": "modifier",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": []
+ },
+ "tone2": {
+ "unicode": "1F3FC",
+ "unicode_alternates": "",
+ "name": "emoji modifier Fitzpatrick type-3",
+ "shortname": ":tone2:",
+ "category": "modifier",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": []
+ },
+ "tone3": {
+ "unicode": "1F3FD",
+ "unicode_alternates": "",
+ "name": "emoji modifier Fitzpatrick type-4",
+ "shortname": ":tone3:",
+ "category": "modifier",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": []
+ },
+ "tone4": {
+ "unicode": "1F3FE",
+ "unicode_alternates": "",
+ "name": "emoji modifier Fitzpatrick type-5",
+ "shortname": ":tone4:",
+ "category": "modifier",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": []
+ },
+ "tone5": {
+ "unicode": "1F3FF",
+ "unicode_alternates": "",
+ "name": "emoji modifier Fitzpatrick type-6",
+ "shortname": ":tone5:",
+ "category": "modifier",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": []
+ },
"tongue": {
"unicode": "1F445",
"unicode_alternates": [],
@@ -12269,7 +27264,25 @@
"category": "emoticons",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["mouth", "playful", "tongue", "mouth", "taste", "buds", "food", "silly", "playful", "tease", "kiss", "french kiss", "lick", "tasty", "playfulness", "silliness", "intimacy"],
+ "keywords": [
+ "mouth",
+ "playful",
+ "tongue",
+ "mouth",
+ "taste",
+ "buds",
+ "food",
+ "silly",
+ "playful",
+ "tease",
+ "kiss",
+ "french kiss",
+ "lick",
+ "tasty",
+ "playfulness",
+ "silliness",
+ "intimacy"
+ ],
"moji": "👅"
},
"tools": {
@@ -12278,9 +27291,13 @@
"name": "hammer and wrench",
"shortname": ":tools:",
"category": "objects_symbols",
- "aliases": [":hammer_and_wrench:"],
+ "aliases": [
+ ":hammer_and_wrench:"
+ ],
"aliases_ascii": [],
- "keywords": ["tools"]
+ "keywords": [
+ "tools"
+ ]
},
"top": {
"unicode": "1F51D",
@@ -12290,7 +27307,10 @@
"category": "other",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["blue-square", "words"],
+ "keywords": [
+ "blue-square",
+ "words"
+ ],
"moji": "🔝"
},
"tophat": {
@@ -12301,9 +27321,63 @@
"category": "emoticons",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["classy", "gentleman", "magic", "top", "hat", "cap", "beaver", "high", "tall", "stove", "pipe", "chimney", "topper", "london", "period piece", "magic", "magician"],
+ "keywords": [
+ "classy",
+ "gentleman",
+ "magic",
+ "top",
+ "hat",
+ "cap",
+ "beaver",
+ "high",
+ "tall",
+ "stove",
+ "pipe",
+ "chimney",
+ "topper",
+ "london",
+ "period piece",
+ "magic",
+ "magician"
+ ],
"moji": "🎩"
},
+ "track_next": {
+ "unicode": "23ED",
+ "unicode_alternates": "",
+ "name": "black right-pointing double triangle with vertical bar",
+ "shortname": ":track_next:",
+ "category": "symbols",
+ "aliases": [
+ ":next_track:"
+ ],
+ "aliases_ascii": [],
+ "keywords": [
+ "arrow",
+ "next scene",
+ "next track",
+ "sound",
+ "symbol"
+ ]
+ },
+ "track_previous": {
+ "unicode": "23EE",
+ "unicode_alternates": "",
+ "name": "black left-pointing double triangle with vertical bar",
+ "shortname": ":track_previous:",
+ "category": "symbols",
+ "aliases": [
+ ":previous_track:"
+ ],
+ "aliases_ascii": [],
+ "keywords": [
+ "arrow",
+ "previous scene",
+ "previous track",
+ "sound",
+ "symbol"
+ ]
+ },
"trackball": {
"unicode": "1F5B2",
"unicode_alternates": [],
@@ -12312,7 +27386,11 @@
"category": "objects_symbols",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["input", "device", "gadget"]
+ "keywords": [
+ "input",
+ "device",
+ "gadget"
+ ]
},
"tractor": {
"unicode": "1F69C",
@@ -12322,7 +27400,17 @@
"category": "places",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["agriculture", "car", "farming", "vehicle", "tractor", "farm", "construction", "machine", "digger"],
+ "keywords": [
+ "agriculture",
+ "car",
+ "farming",
+ "vehicle",
+ "tractor",
+ "farm",
+ "construction",
+ "machine",
+ "digger"
+ ],
"moji": "🚜"
},
"traffic_light": {
@@ -12333,7 +27421,16 @@
"category": "places",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["traffic", "transportation", "traffic", "light", "stop", "go", "yield", "horizontal"],
+ "keywords": [
+ "traffic",
+ "transportation",
+ "traffic",
+ "light",
+ "stop",
+ "go",
+ "yield",
+ "horizontal"
+ ],
"moji": "🚥"
},
"train": {
@@ -12344,7 +27441,10 @@
"category": "places",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["tram", "rail"]
+ "keywords": [
+ "tram",
+ "rail"
+ ]
},
"train2": {
"unicode": "1F686",
@@ -12354,7 +27454,13 @@
"category": "places",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["transportation", "vehicle", "train", "locomotive", "rail"],
+ "keywords": [
+ "transportation",
+ "vehicle",
+ "train",
+ "locomotive",
+ "rail"
+ ],
"moji": "🚆"
},
"train_diesel": {
@@ -12363,9 +27469,16 @@
"name": "diesel locomotive",
"shortname": ":train_diesel:",
"category": "travel_places",
- "aliases": [":diesel_locomotive:"],
+ "aliases": [
+ ":diesel_locomotive:"
+ ],
"aliases_ascii": [],
- "keywords": ["train", "transportation", "engine", "rail"]
+ "keywords": [
+ "train",
+ "transportation",
+ "engine",
+ "rail"
+ ]
},
"tram": {
"unicode": "1F68A",
@@ -12375,7 +27488,13 @@
"category": "places",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["transportation", "vehicle", "tram", "transportation", "transport"],
+ "keywords": [
+ "transportation",
+ "vehicle",
+ "tram",
+ "transportation",
+ "transport"
+ ],
"moji": "🚊"
},
"triangle_round": {
@@ -12384,9 +27503,15 @@
"name": "triangle with rounded corners",
"shortname": ":triangle_round:",
"category": "objects_symbols",
- "aliases": [":triangle_with_rounded_corners:"],
+ "aliases": [
+ ":triangle_with_rounded_corners:"
+ ],
"aliases_ascii": [],
- "keywords": ["caution", "warning", "alert"]
+ "keywords": [
+ "caution",
+ "warning",
+ "alert"
+ ]
},
"triangular_flag_on_post": {
"unicode": "1F6A9",
@@ -12396,7 +27521,14 @@
"category": "places",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["triangle", "triangular", "flag", "golf", "post", "flagpole"],
+ "keywords": [
+ "triangle",
+ "triangular",
+ "flag",
+ "golf",
+ "post",
+ "flagpole"
+ ],
"moji": "🚩"
},
"triangular_ruler": {
@@ -12407,7 +27539,12 @@
"category": "objects",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["architect", "math", "sketch", "stationery"],
+ "keywords": [
+ "architect",
+ "math",
+ "sketch",
+ "stationery"
+ ],
"moji": "📐"
},
"trident": {
@@ -12418,7 +27555,10 @@
"category": "other",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["spear", "weapon"],
+ "keywords": [
+ "spear",
+ "weapon"
+ ],
"moji": "🔱"
},
"triumph": {
@@ -12429,7 +27569,14 @@
"category": "emoticons",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["face", "gas", "phew", "triumph", "steam", "breath"],
+ "keywords": [
+ "face",
+ "gas",
+ "phew",
+ "triumph",
+ "steam",
+ "breath"
+ ],
"moji": "😤"
},
"trolleybus": {
@@ -12440,7 +27587,16 @@
"category": "places",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["bart", "transportation", "vehicle", "trolley", "bus", "city", "transport", "transportation"],
+ "keywords": [
+ "bart",
+ "transportation",
+ "vehicle",
+ "trolley",
+ "bus",
+ "city",
+ "transport",
+ "transportation"
+ ],
"moji": "🚎"
},
"trophy": {
@@ -12451,7 +27607,22 @@
"category": "objects",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["award", "ceremony", "contest", "ftw", "place", "win", "trophy", "first", "show", "place", "win", "reward", "achievement", "medal"],
+ "keywords": [
+ "award",
+ "ceremony",
+ "contest",
+ "ftw",
+ "place",
+ "win",
+ "trophy",
+ "first",
+ "show",
+ "place",
+ "win",
+ "reward",
+ "achievement",
+ "medal"
+ ],
"moji": "🏆"
},
"tropical_drink": {
@@ -12462,7 +27633,17 @@
"category": "objects",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["beverage", "tropical", "drink", "mixed", "pineapple", "coconut", "pina", "fruit", "umbrella"],
+ "keywords": [
+ "beverage",
+ "tropical",
+ "drink",
+ "mixed",
+ "pineapple",
+ "coconut",
+ "pina",
+ "fruit",
+ "umbrella"
+ ],
"moji": "🍹"
},
"tropical_fish": {
@@ -12473,7 +27654,10 @@
"category": "nature",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["animal", "swim"],
+ "keywords": [
+ "animal",
+ "swim"
+ ],
"moji": "🐠"
},
"truck": {
@@ -12484,7 +27668,13 @@
"category": "places",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["cars", "transportation", "truck", "delivery", "package"],
+ "keywords": [
+ "cars",
+ "transportation",
+ "truck",
+ "delivery",
+ "package"
+ ],
"moji": "🚚"
},
"trumpet": {
@@ -12495,7 +27685,14 @@
"category": "objects",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["brass", "music", "trumpet", "brass", "music", "instrument"],
+ "keywords": [
+ "brass",
+ "music",
+ "trumpet",
+ "brass",
+ "music",
+ "instrument"
+ ],
"moji": "🎺"
},
"tulip": {
@@ -12506,18 +27703,42 @@
"category": "nature",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["flowers", "nature", "plant", "tulip", "flower", "bulb", "spring", "easter"],
+ "keywords": [
+ "flowers",
+ "nature",
+ "plant",
+ "tulip",
+ "flower",
+ "bulb",
+ "spring",
+ "easter"
+ ],
"moji": "🌷"
},
+ "turkey": {
+ "unicode": "1F983",
+ "unicode_alternates": "",
+ "name": "turkey",
+ "shortname": ":turkey:",
+ "category": "nature",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": []
+ },
"turned_ok_hand": {
"unicode": "1F58F",
"unicode_alternates": [],
"name": "turned ok hand sign",
"shortname": ":turned_ok_hand:",
"category": "people",
- "aliases": [":turned_ok_hand_sign:"],
+ "aliases": [
+ ":turned_ok_hand_sign:"
+ ],
"aliases_ascii": [],
- "keywords": ["perfect", "okay"]
+ "keywords": [
+ "perfect",
+ "okay"
+ ]
},
"turtle": {
"unicode": "1F422",
@@ -12527,9 +27748,39 @@
"category": "nature",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["animal", "slow", "turtle", "shell", "tortoise", "chelonian", "reptile", "slow", "snap", "steady"],
+ "keywords": [
+ "animal",
+ "slow",
+ "turtle",
+ "shell",
+ "tortoise",
+ "chelonian",
+ "reptile",
+ "slow",
+ "snap",
+ "steady"
+ ],
"moji": "🐢"
},
+ "tv": {
+ "unicode": "1F4FA",
+ "unicode_alternates": "",
+ "name": "television",
+ "shortname": ":tv:",
+ "category": "objects",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": [
+ "oldschool",
+ "program",
+ "show",
+ "technology",
+ "tv",
+ "entertainment",
+ "object",
+ "video"
+ ]
+ },
"twisted_rightwards_arrows": {
"unicode": "1F500",
"unicode_alternates": [],
@@ -12538,19 +27789,28 @@
"category": "other",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["blue-square"],
+ "keywords": [
+ "blue-square"
+ ],
"moji": "🔀"
},
"two": {
"moji": "2️⃣",
"unicode": "0032-20E3",
- "unicode_alternates": ["0032-FE0F-20E3"],
+ "unicode_alternates": [
+ "0032-FE0F-20E3"
+ ],
"name": "digit two",
"shortname": ":two:",
"category": "other",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["2", "blue-square", "numbers", "prime"]
+ "keywords": [
+ "2",
+ "blue-square",
+ "numbers",
+ "prime"
+ ]
},
"two_hearts": {
"unicode": "1F495",
@@ -12560,7 +27820,17 @@
"category": "emoticons",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["affection", "like", "love", "valentines", "heart", "hearts", "two", "love", "emotion"],
+ "keywords": [
+ "affection",
+ "like",
+ "love",
+ "valentines",
+ "heart",
+ "hearts",
+ "two",
+ "love",
+ "emotion"
+ ],
"moji": "💕"
},
"two_men_holding_hands": {
@@ -12571,7 +27841,21 @@
"category": "emoticons",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["bromance", "couple", "friends", "like", "love", "men", "gay", "homosexual", "friends", "hands", "holding", "team", "unity"],
+ "keywords": [
+ "bromance",
+ "couple",
+ "friends",
+ "like",
+ "love",
+ "men",
+ "gay",
+ "homosexual",
+ "friends",
+ "hands",
+ "holding",
+ "team",
+ "unity"
+ ],
"moji": "👬"
},
"two_women_holding_hands": {
@@ -12582,7 +27866,24 @@
"category": "emoticons",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["couple", "female", "friends", "like", "love", "women", "hands", "girlfriends", "friends", "sisters", "mother", "daughter", "gay", "homosexual", "couple", "unity"],
+ "keywords": [
+ "couple",
+ "female",
+ "friends",
+ "like",
+ "love",
+ "women",
+ "hands",
+ "girlfriends",
+ "friends",
+ "sisters",
+ "mother",
+ "daughter",
+ "gay",
+ "homosexual",
+ "couple",
+ "unity"
+ ],
"moji": "👭"
},
"u5272": {
@@ -12593,7 +27894,13 @@
"category": "other",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["chinese", "cut", "divide", "kanji", "pink"],
+ "keywords": [
+ "chinese",
+ "cut",
+ "divide",
+ "kanji",
+ "pink"
+ ],
"moji": "🈹"
},
"u5408": {
@@ -12604,7 +27911,12 @@
"category": "other",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["chinese", "japanese", "join", "kanji"],
+ "keywords": [
+ "chinese",
+ "japanese",
+ "join",
+ "kanji"
+ ],
"moji": "🈴"
},
"u55b6": {
@@ -12615,18 +27927,28 @@
"category": "other",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["japanese", "opening hours"],
+ "keywords": [
+ "japanese",
+ "opening hours"
+ ],
"moji": "🈺"
},
"u6307": {
"unicode": "1F22F",
- "unicode_alternates": ["1F22F-FE0F"],
+ "unicode_alternates": [
+ "1F22F-FE0F"
+ ],
"name": "squared cjk unified ideograph-6307",
"shortname": ":u6307:",
"category": "other",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["chinese", "green-square", "kanji", "point"],
+ "keywords": [
+ "chinese",
+ "green-square",
+ "kanji",
+ "point"
+ ],
"moji": "🈯"
},
"u6708": {
@@ -12637,7 +27959,13 @@
"category": "other",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["chinese", "japanese", "kanji", "moon", "orange-square"],
+ "keywords": [
+ "chinese",
+ "japanese",
+ "kanji",
+ "moon",
+ "orange-square"
+ ],
"moji": "🈷"
},
"u6709": {
@@ -12648,7 +27976,12 @@
"category": "other",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["chinese", "have", "kanji", "orange-square"],
+ "keywords": [
+ "chinese",
+ "have",
+ "kanji",
+ "orange-square"
+ ],
"moji": "🈶"
},
"u6e80": {
@@ -12659,18 +27992,33 @@
"category": "other",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["chinese", "full", "japanese", "kanji", "red-square"],
+ "keywords": [
+ "chinese",
+ "full",
+ "japanese",
+ "kanji",
+ "red-square"
+ ],
"moji": "🈵"
},
"u7121": {
"unicode": "1F21A",
- "unicode_alternates": ["1F21A-FE0F"],
+ "unicode_alternates": [
+ "1F21A-FE0F"
+ ],
"name": "squared cjk unified ideograph-7121",
"shortname": ":u7121:",
"category": "other",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["chinese", "japanese", "kanji", "no", "nothing", "orange-square"],
+ "keywords": [
+ "chinese",
+ "japanese",
+ "kanji",
+ "no",
+ "nothing",
+ "orange-square"
+ ],
"moji": "🈚"
},
"u7533": {
@@ -12681,7 +28029,11 @@
"category": "other",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["chinese", "japanese", "kanji"],
+ "keywords": [
+ "chinese",
+ "japanese",
+ "kanji"
+ ],
"moji": "🈸"
},
"u7981": {
@@ -12692,7 +28044,14 @@
"category": "other",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["chinese", "forbidden", "japanese", "kanji", "limit", "restricted"],
+ "keywords": [
+ "chinese",
+ "forbidden",
+ "japanese",
+ "kanji",
+ "limit",
+ "restricted"
+ ],
"moji": "🈲"
},
"u7a7a": {
@@ -12703,20 +28062,45 @@
"category": "other",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["chinese", "empty", "japanese", "kanji"],
+ "keywords": [
+ "chinese",
+ "empty",
+ "japanese",
+ "kanji"
+ ],
"moji": "🈳"
},
"umbrella": {
"unicode": "2614",
- "unicode_alternates": ["2614-FE0F"],
+ "unicode_alternates": [
+ "2614-FE0F"
+ ],
"name": "umbrella with rain drops",
"shortname": ":umbrella:",
"category": "nature",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["rain", "weather"],
+ "keywords": [
+ "rain",
+ "weather"
+ ],
"moji": "☔"
},
+ "umbrella2": {
+ "unicode": "2602",
+ "unicode_alternates": "",
+ "name": "umbrella",
+ "shortname": ":umbrella2:",
+ "category": "nature",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": [
+ "clothing",
+ "nature",
+ "rain",
+ "weather"
+ ]
+ },
"unamused": {
"unicode": "1F612",
"unicode_alternates": [],
@@ -12725,7 +28109,19 @@
"category": "emoticons",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["bored", "face", "indifference", "serious", "straight face", "unamused", "not amused", "depressed", "unhappy", "disapprove", "lame"],
+ "keywords": [
+ "bored",
+ "face",
+ "indifference",
+ "serious",
+ "straight face",
+ "unamused",
+ "not amused",
+ "depressed",
+ "unhappy",
+ "disapprove",
+ "lame"
+ ],
"moji": "😒"
},
"underage": {
@@ -12736,9 +28132,26 @@
"category": "other",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["18", "drink", "night", "pub"],
+ "keywords": [
+ "18",
+ "drink",
+ "night",
+ "pub"
+ ],
"moji": "🔞"
},
+ "unicorn": {
+ "unicode": "1F984",
+ "unicode_alternates": "",
+ "name": "unicorn face",
+ "shortname": ":unicorn:",
+ "category": "nature",
+ "aliases": [
+ ":unicorn_face:"
+ ],
+ "aliases_ascii": [],
+ "keywords": []
+ },
"unlock": {
"unicode": "1F513",
"unicode_alternates": [],
@@ -12747,7 +28160,10 @@
"category": "objects",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["privacy", "security"],
+ "keywords": [
+ "privacy",
+ "security"
+ ],
"moji": "🔓"
},
"up": {
@@ -12758,20 +28174,138 @@
"category": "other",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["blue-square"],
+ "keywords": [
+ "blue-square"
+ ],
"moji": "🆙"
},
+ "upside_down": {
+ "unicode": "1F643",
+ "unicode_alternates": "",
+ "name": "upside-down face",
+ "shortname": ":upside_down:",
+ "category": "people",
+ "aliases": [
+ ":upside_down_face:"
+ ],
+ "aliases_ascii": [],
+ "keywords": []
+ },
+ "urn": {
+ "unicode": "26B1",
+ "unicode_alternates": "",
+ "name": "funeral urn",
+ "shortname": ":urn:",
+ "category": "objects",
+ "aliases": [
+ ":funeral_urn:"
+ ],
+ "aliases_ascii": [],
+ "keywords": [
+ "death",
+ "object"
+ ]
+ },
"v": {
"unicode": "270C",
- "unicode_alternates": ["270C-FE0F"],
+ "unicode_alternates": [
+ "270C-FE0F"
+ ],
"name": "victory hand",
"shortname": ":v:",
"category": "emoticons",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["fingers", "hand", "ohyeah", "peace", "two", "victory"],
+ "keywords": [
+ "fingers",
+ "hand",
+ "ohyeah",
+ "peace",
+ "two",
+ "victory"
+ ],
"moji": "✌"
},
+ "v_tone1": {
+ "unicode": "270C-1F3FB",
+ "unicode_alternates": "",
+ "name": "victory hand tone 1",
+ "shortname": ":v_tone1:",
+ "category": "people",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": [
+ "fingers",
+ "ohyeah",
+ "peace",
+ "two",
+ "v"
+ ]
+ },
+ "v_tone2": {
+ "unicode": "270C-1F3FC",
+ "unicode_alternates": "",
+ "name": "victory hand tone 2",
+ "shortname": ":v_tone2:",
+ "category": "people",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": [
+ "fingers",
+ "ohyeah",
+ "peace",
+ "two",
+ "v"
+ ]
+ },
+ "v_tone3": {
+ "unicode": "270C-1F3FD",
+ "unicode_alternates": "",
+ "name": "victory hand tone 3",
+ "shortname": ":v_tone3:",
+ "category": "people",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": [
+ "fingers",
+ "ohyeah",
+ "peace",
+ "two",
+ "v"
+ ]
+ },
+ "v_tone4": {
+ "unicode": "270C-1F3FE",
+ "unicode_alternates": "",
+ "name": "victory hand tone 4",
+ "shortname": ":v_tone4:",
+ "category": "people",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": [
+ "fingers",
+ "ohyeah",
+ "peace",
+ "two",
+ "v"
+ ]
+ },
+ "v_tone5": {
+ "unicode": "270C-1F3FF",
+ "unicode_alternates": "",
+ "name": "victory hand tone 5",
+ "shortname": ":v_tone5:",
+ "category": "people",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": [
+ "fingers",
+ "ohyeah",
+ "peace",
+ "two",
+ "v"
+ ]
+ },
"vertical_traffic_light": {
"unicode": "1F6A6",
"unicode_alternates": [],
@@ -12780,7 +28314,15 @@
"category": "places",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["transportation", "traffic", "light", "stop", "go", "yield", "vertical"],
+ "keywords": [
+ "transportation",
+ "traffic",
+ "light",
+ "stop",
+ "go",
+ "yield",
+ "vertical"
+ ],
"moji": "🚦"
},
"vhs": {
@@ -12791,7 +28333,11 @@
"category": "objects",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["oldschool", "record", "video"],
+ "keywords": [
+ "oldschool",
+ "record",
+ "video"
+ ],
"moji": "📼"
},
"vibration_mode": {
@@ -12802,7 +28348,10 @@
"category": "other",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["orange-square", "phone"],
+ "keywords": [
+ "orange-square",
+ "phone"
+ ],
"moji": "📳"
},
"video_camera": {
@@ -12813,7 +28362,10 @@
"category": "objects",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["film", "record"],
+ "keywords": [
+ "film",
+ "record"
+ ],
"moji": "📹"
},
"video_game": {
@@ -12824,7 +28376,19 @@
"category": "objects",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["PS4", "console", "controller", "play", "video", "game", "console", "controller", "nintendo", "xbox", "playstation"],
+ "keywords": [
+ "PS4",
+ "console",
+ "controller",
+ "play",
+ "video",
+ "game",
+ "console",
+ "controller",
+ "nintendo",
+ "xbox",
+ "playstation"
+ ],
"moji": "🎮"
},
"violin": {
@@ -12835,18 +28399,39 @@
"category": "objects",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["instrument", "music", "violin", "fiddle", "music", "instrument"],
+ "keywords": [
+ "instrument",
+ "music",
+ "violin",
+ "fiddle",
+ "music",
+ "instrument"
+ ],
"moji": "🎻"
},
"virgo": {
"unicode": "264D",
- "unicode_alternates": ["264D-FE0F"],
+ "unicode_alternates": [
+ "264D-FE0F"
+ ],
"name": "virgo",
"shortname": ":virgo:",
"category": "other",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["sign", "virgo", "maiden", "astrology", "greek", "constellation", "stars", "zodiac", "sign", "zodiac", "horoscope"],
+ "keywords": [
+ "sign",
+ "virgo",
+ "maiden",
+ "astrology",
+ "greek",
+ "constellation",
+ "stars",
+ "zodiac",
+ "sign",
+ "zodiac",
+ "horoscope"
+ ],
"moji": "♍"
},
"volcano": {
@@ -12857,9 +28442,27 @@
"category": "nature",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["nature", "photo", "volcano", "lava", "magma", "hot", "explode"],
+ "keywords": [
+ "nature",
+ "photo",
+ "volcano",
+ "lava",
+ "magma",
+ "hot",
+ "explode"
+ ],
"moji": "🌋"
},
+ "volleyball": {
+ "unicode": "1F3D0",
+ "unicode_alternates": "",
+ "name": "volleyball",
+ "shortname": ":volleyball:",
+ "category": "activity",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": []
+ },
"vs": {
"unicode": "1F19A",
"unicode_alternates": [],
@@ -12868,7 +28471,10 @@
"category": "other",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["orange-square", "words"],
+ "keywords": [
+ "orange-square",
+ "words"
+ ],
"moji": "🆚"
},
"vulcan": {
@@ -12877,9 +28483,113 @@
"name": "raised hand with part between middle and ring fingers",
"shortname": ":vulcan:",
"category": "people",
- "aliases": [":raised_hand_with_part_between_middle_and_ring_fingers:"],
- "aliases_ascii": [],
- "keywords": ["vulcan", "spock", "leonard", "nimoy", "star trek", "live long"]
+ "aliases": [
+ ":raised_hand_with_part_between_middle_and_ring_fingers:"
+ ],
+ "aliases_ascii": [],
+ "keywords": [
+ "vulcan",
+ "spock",
+ "leonard",
+ "nimoy",
+ "star trek",
+ "live long"
+ ]
+ },
+ "vulcan_tone1": {
+ "unicode": "1F596-1F3FB",
+ "unicode_alternates": "",
+ "name": "raised hand with part between middle and ring fingers tone 1",
+ "shortname": ":vulcan_tone1:",
+ "category": "people",
+ "aliases": [
+ ":raised_hand_with_part_between_middle_and_ring_fingers_tone1:"
+ ],
+ "aliases_ascii": [],
+ "keywords": [
+ "vulcan",
+ "spock",
+ "leonard",
+ "nimoy",
+ "star trek",
+ "live long"
+ ]
+ },
+ "vulcan_tone2": {
+ "unicode": "1F596-1F3FC",
+ "unicode_alternates": "",
+ "name": "raised hand with part between middle and ring fingers tone 2",
+ "shortname": ":vulcan_tone2:",
+ "category": "people",
+ "aliases": [
+ ":raised_hand_with_part_between_middle_and_ring_fingers_tone2:"
+ ],
+ "aliases_ascii": [],
+ "keywords": [
+ "vulcan",
+ "spock",
+ "leonard",
+ "nimoy",
+ "star trek",
+ "live long"
+ ]
+ },
+ "vulcan_tone3": {
+ "unicode": "1F596-1F3FD",
+ "unicode_alternates": "",
+ "name": "raised hand with part between middle and ring fingers tone 3",
+ "shortname": ":vulcan_tone3:",
+ "category": "people",
+ "aliases": [
+ ":raised_hand_with_part_between_middle_and_ring_fingers_tone3:"
+ ],
+ "aliases_ascii": [],
+ "keywords": [
+ "vulcan",
+ "spock",
+ "leonard",
+ "nimoy",
+ "star trek",
+ "live long"
+ ]
+ },
+ "vulcan_tone4": {
+ "unicode": "1F596-1F3FE",
+ "unicode_alternates": "",
+ "name": "raised hand with part between middle and ring fingers tone 4",
+ "shortname": ":vulcan_tone4:",
+ "category": "people",
+ "aliases": [
+ ":raised_hand_with_part_between_middle_and_ring_fingers_tone4:"
+ ],
+ "aliases_ascii": [],
+ "keywords": [
+ "vulcan",
+ "spock",
+ "leonard",
+ "nimoy",
+ "star trek",
+ "live long"
+ ]
+ },
+ "vulcan_tone5": {
+ "unicode": "1F596-1F3FF",
+ "unicode_alternates": "",
+ "name": "raised hand with part between middle and ring fingers tone 5",
+ "shortname": ":vulcan_tone5:",
+ "category": "people",
+ "aliases": [
+ ":raised_hand_with_part_between_middle_and_ring_fingers_tone5:"
+ ],
+ "aliases_ascii": [],
+ "keywords": [
+ "vulcan",
+ "spock",
+ "leonard",
+ "nimoy",
+ "star trek",
+ "live long"
+ ]
},
"walking": {
"unicode": "1F6B6",
@@ -12889,9 +28599,103 @@
"category": "emoticons",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["human", "man", "walk", "pedestrian", "stroll", "stride", "foot", "feet"],
+ "keywords": [
+ "human",
+ "man",
+ "walk",
+ "pedestrian",
+ "stroll",
+ "stride",
+ "foot",
+ "feet"
+ ],
"moji": "🚶"
},
+ "walking_tone1": {
+ "unicode": "1F6B6-1F3FB",
+ "unicode_alternates": "",
+ "name": "pedestrian tone 1",
+ "shortname": ":walking_tone1:",
+ "category": "people",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": [
+ "man",
+ "walk",
+ "stroll",
+ "stride",
+ "hiking",
+ "hike"
+ ]
+ },
+ "walking_tone2": {
+ "unicode": "1F6B6-1F3FC",
+ "unicode_alternates": "",
+ "name": "pedestrian tone 2",
+ "shortname": ":walking_tone2:",
+ "category": "people",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": [
+ "man",
+ "walk",
+ "stroll",
+ "stride",
+ "hiking",
+ "hike"
+ ]
+ },
+ "walking_tone3": {
+ "unicode": "1F6B6-1F3FD",
+ "unicode_alternates": "",
+ "name": "pedestrian tone 3",
+ "shortname": ":walking_tone3:",
+ "category": "people",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": [
+ "man",
+ "walk",
+ "stroll",
+ "stride",
+ "hiking",
+ "hike"
+ ]
+ },
+ "walking_tone4": {
+ "unicode": "1F6B6-1F3FE",
+ "unicode_alternates": "",
+ "name": "pedestrian tone 4",
+ "shortname": ":walking_tone4:",
+ "category": "people",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": [
+ "man",
+ "walk",
+ "stroll",
+ "stride",
+ "hiking",
+ "hike"
+ ]
+ },
+ "walking_tone5": {
+ "unicode": "1F6B6-1F3FF",
+ "unicode_alternates": "",
+ "name": "pedestrian tone 5",
+ "shortname": ":walking_tone5:",
+ "category": "people",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": [
+ "man",
+ "walk",
+ "stroll",
+ "stride",
+ "hiking",
+ "hike"
+ ]
+ },
"waning_crescent_moon": {
"unicode": "1F318",
"unicode_alternates": [],
@@ -12900,7 +28704,16 @@
"category": "nature",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["nature", "moon", "crescent", "waning", "sky", "night", "cheese", "phase"],
+ "keywords": [
+ "nature",
+ "moon",
+ "crescent",
+ "waning",
+ "sky",
+ "night",
+ "cheese",
+ "phase"
+ ],
"moji": "🌘"
},
"waning_gibbous_moon": {
@@ -12911,18 +28724,32 @@
"category": "nature",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["nature", "moon", "waning", "gibbous", "sky", "night", "cheese", "phase"],
+ "keywords": [
+ "nature",
+ "moon",
+ "waning",
+ "gibbous",
+ "sky",
+ "night",
+ "cheese",
+ "phase"
+ ],
"moji": "🌖"
},
"warning": {
"unicode": "26A0",
- "unicode_alternates": ["26A0-FE0F"],
+ "unicode_alternates": [
+ "26A0-FE0F"
+ ],
"name": "warning sign",
"shortname": ":warning:",
"category": "places",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["exclamation", "wip"],
+ "keywords": [
+ "exclamation",
+ "wip"
+ ],
"moji": "⚠"
},
"wastebasket": {
@@ -12933,17 +28760,26 @@
"category": "objects_symbols",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["trash", "garbage", "dispose"]
+ "keywords": [
+ "trash",
+ "garbage",
+ "dispose"
+ ]
},
"watch": {
"unicode": "231A",
- "unicode_alternates": ["231A-FE0F"],
+ "unicode_alternates": [
+ "231A-FE0F"
+ ],
"name": "watch",
"shortname": ":watch:",
"category": "objects",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["accessories", "time"],
+ "keywords": [
+ "accessories",
+ "time"
+ ],
"moji": "⌚"
},
"water_buffalo": {
@@ -12954,7 +28790,18 @@
"category": "nature",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["animal", "cow", "nature", "ox", "water", "buffalo", "asia", "bovine", "milk", "dairy"],
+ "keywords": [
+ "animal",
+ "cow",
+ "nature",
+ "ox",
+ "water",
+ "buffalo",
+ "asia",
+ "bovine",
+ "milk",
+ "dairy"
+ ],
"moji": "🐃"
},
"watermelon": {
@@ -12965,7 +28812,15 @@
"category": "objects",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["food", "fruit", "melon", "watermelon", "summer", "fruit", "large"],
+ "keywords": [
+ "food",
+ "fruit",
+ "melon",
+ "watermelon",
+ "summer",
+ "fruit",
+ "large"
+ ],
"moji": "🍉"
},
"wave": {
@@ -12976,9 +28831,100 @@
"category": "emoticons",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["farewell", "gesture", "goodbye", "hands", "solong"],
+ "keywords": [
+ "farewell",
+ "gesture",
+ "goodbye",
+ "hands",
+ "solong"
+ ],
"moji": "👋"
},
+ "wave_tone1": {
+ "unicode": "1F44B-1F3FB",
+ "unicode_alternates": "",
+ "name": "waving hand sign tone 1",
+ "shortname": ":wave_tone1:",
+ "category": "people",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": [
+ "farewell",
+ "gesture",
+ "goodbye",
+ "solong",
+ "hi",
+ "wave"
+ ]
+ },
+ "wave_tone2": {
+ "unicode": "1F44B-1F3FC",
+ "unicode_alternates": "",
+ "name": "waving hand sign tone 2",
+ "shortname": ":wave_tone2:",
+ "category": "people",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": [
+ "farewell",
+ "gesture",
+ "goodbye",
+ "solong",
+ "hi",
+ "wave"
+ ]
+ },
+ "wave_tone3": {
+ "unicode": "1F44B-1F3FD",
+ "unicode_alternates": "",
+ "name": "waving hand sign tone 3",
+ "shortname": ":wave_tone3:",
+ "category": "people",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": [
+ "farewell",
+ "gesture",
+ "goodbye",
+ "solong",
+ "hi",
+ "wave"
+ ]
+ },
+ "wave_tone4": {
+ "unicode": "1F44B-1F3FE",
+ "unicode_alternates": "",
+ "name": "waving hand sign tone 4",
+ "shortname": ":wave_tone4:",
+ "category": "people",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": [
+ "farewell",
+ "gesture",
+ "goodbye",
+ "solong",
+ "hi",
+ "wave"
+ ]
+ },
+ "wave_tone5": {
+ "unicode": "1F44B-1F3FF",
+ "unicode_alternates": "",
+ "name": "waving hand sign tone 5",
+ "shortname": ":wave_tone5:",
+ "category": "people",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": [
+ "farewell",
+ "gesture",
+ "goodbye",
+ "solong",
+ "hi",
+ "wave"
+ ]
+ },
"wavy_dash": {
"unicode": "3030",
"unicode_alternates": [],
@@ -12987,7 +28933,10 @@
"category": "other",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["draw", "line"],
+ "keywords": [
+ "draw",
+ "line"
+ ],
"moji": "〰"
},
"waxing_crescent_moon": {
@@ -12998,7 +28947,15 @@
"category": "nature",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["nature", "moon", "waxing", "sky", "night", "cheese", "phase"],
+ "keywords": [
+ "nature",
+ "moon",
+ "waxing",
+ "sky",
+ "night",
+ "cheese",
+ "phase"
+ ],
"moji": "🌒"
},
"waxing_gibbous_moon": {
@@ -13009,7 +28966,9 @@
"category": "nature",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["nature"],
+ "keywords": [
+ "nature"
+ ],
"moji": "🌔"
},
"wc": {
@@ -13020,7 +28979,20 @@
"category": "other",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["blue-square", "restroom", "toilet", "water", "closet", "toilet", "bathroom", "throne", "porcelain", "waste", "flush", "plumbing"],
+ "keywords": [
+ "blue-square",
+ "restroom",
+ "toilet",
+ "water",
+ "closet",
+ "toilet",
+ "bathroom",
+ "throne",
+ "porcelain",
+ "waste",
+ "flush",
+ "plumbing"
+ ],
"moji": "🚾"
},
"weary": {
@@ -13031,7 +29003,21 @@
"category": "emoticons",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["face", "frustrated", "sad", "sleepy", "tired", "weary", "sleepy", "tired", "tiredness", "study", "finals", "school", "exhausted"],
+ "keywords": [
+ "face",
+ "frustrated",
+ "sad",
+ "sleepy",
+ "tired",
+ "weary",
+ "sleepy",
+ "tired",
+ "tiredness",
+ "study",
+ "finals",
+ "school",
+ "exhausted"
+ ],
"moji": "😩"
},
"wedding": {
@@ -13042,7 +29028,15 @@
"category": "places",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["affection", "bride", "couple", "groom", "like", "love", "marriage"],
+ "keywords": [
+ "affection",
+ "bride",
+ "couple",
+ "groom",
+ "like",
+ "love",
+ "marriage"
+ ],
"moji": "💒"
},
"whale": {
@@ -13053,7 +29047,12 @@
"category": "nature",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["animal", "nature", "ocean", "sea"],
+ "keywords": [
+ "animal",
+ "nature",
+ "ocean",
+ "sea"
+ ],
"moji": "🐳"
},
"whale2": {
@@ -13064,18 +29063,48 @@
"category": "nature",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["animal", "nature", "ocean", "sea", "whale", "blubber", "bloated", "fat", "large", "massive"],
+ "keywords": [
+ "animal",
+ "nature",
+ "ocean",
+ "sea",
+ "whale",
+ "blubber",
+ "bloated",
+ "fat",
+ "large",
+ "massive"
+ ],
"moji": "🐋"
},
+ "wheel_of_dharma": {
+ "unicode": "2638",
+ "unicode_alternates": "",
+ "name": "wheel of dharma",
+ "shortname": ":wheel_of_dharma:",
+ "category": "symbols",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": [
+ "buddhist",
+ "religion",
+ "symbol"
+ ]
+ },
"wheelchair": {
"unicode": "267F",
- "unicode_alternates": ["267F-FE0F"],
+ "unicode_alternates": [
+ "267F-FE0F"
+ ],
"name": "wheelchair symbol",
"shortname": ":wheelchair:",
"category": "other",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["blue-square", "disabled"],
+ "keywords": [
+ "blue-square",
+ "disabled"
+ ],
"moji": "♿"
},
"white_check_mark": {
@@ -13086,18 +29115,26 @@
"category": "other",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["agree", "green-square", "ok"],
+ "keywords": [
+ "agree",
+ "green-square",
+ "ok"
+ ],
"moji": "✅"
},
"white_circle": {
"unicode": "26AA",
- "unicode_alternates": ["26AA-FE0F"],
+ "unicode_alternates": [
+ "26AA-FE0F"
+ ],
"name": "medium white circle",
"shortname": ":white_circle:",
"category": "other",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["shape"],
+ "keywords": [
+ "shape"
+ ],
"moji": "⚪"
},
"white_flower": {
@@ -13108,51 +29145,81 @@
"category": "other",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["japanese", "white", "flower", "teacher", "school", "grade", "score", "brilliance", "intelligence", "homework", "student", "assignment", "praise"],
+ "keywords": [
+ "japanese",
+ "white",
+ "flower",
+ "teacher",
+ "school",
+ "grade",
+ "score",
+ "brilliance",
+ "intelligence",
+ "homework",
+ "student",
+ "assignment",
+ "praise"
+ ],
"moji": "💮"
},
"white_large_square": {
"unicode": "2B1C",
- "unicode_alternates": ["2B1C-FE0F"],
+ "unicode_alternates": [
+ "2B1C-FE0F"
+ ],
"name": "white large square",
"shortname": ":white_large_square:",
"category": "other",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["shape"],
+ "keywords": [
+ "shape"
+ ],
"moji": "⬜"
},
"white_medium_small_square": {
"unicode": "25FD",
- "unicode_alternates": ["25FD-FE0F"],
+ "unicode_alternates": [
+ "25FD-FE0F"
+ ],
"name": "white medium small square",
"shortname": ":white_medium_small_square:",
"category": "other",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["shape"],
+ "keywords": [
+ "shape"
+ ],
"moji": "◽"
},
"white_medium_square": {
"unicode": "25FB",
- "unicode_alternates": ["25FB-FE0F"],
+ "unicode_alternates": [
+ "25FB-FE0F"
+ ],
"name": "white medium square",
"shortname": ":white_medium_square:",
"category": "other",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["shape"],
+ "keywords": [
+ "shape"
+ ],
"moji": "◻"
},
"white_small_square": {
"unicode": "25AB",
- "unicode_alternates": ["25AB-FE0F"],
+ "unicode_alternates": [
+ "25AB-FE0F"
+ ],
"name": "white small square",
"shortname": ":white_small_square:",
"category": "other",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["shape"],
+ "keywords": [
+ "shape"
+ ],
"moji": "▫"
},
"white_square_button": {
@@ -13163,9 +29230,56 @@
"category": "other",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["shape"],
+ "keywords": [
+ "shape"
+ ],
"moji": "🔳"
},
+ "white_sun_cloud": {
+ "unicode": "1F325",
+ "unicode_alternates": "",
+ "name": "white sun behind cloud",
+ "shortname": ":white_sun_cloud:",
+ "category": "nature",
+ "aliases": [
+ ":white_sun_behind_cloud:"
+ ],
+ "aliases_ascii": [],
+ "keywords": [
+ "nature",
+ "weather"
+ ]
+ },
+ "white_sun_rain_cloud": {
+ "unicode": "1F326",
+ "unicode_alternates": "",
+ "name": "white sun behind cloud with rain",
+ "shortname": ":white_sun_rain_cloud:",
+ "category": "nature",
+ "aliases": [
+ ":white_sun_behind_cloud_with_rain:"
+ ],
+ "aliases_ascii": [],
+ "keywords": [
+ "nature",
+ "weather"
+ ]
+ },
+ "white_sun_small_cloud": {
+ "unicode": "1F324",
+ "unicode_alternates": "",
+ "name": "white sun with small cloud",
+ "shortname": ":white_sun_small_cloud:",
+ "category": "nature",
+ "aliases": [
+ ":white_sun_with_small_cloud:"
+ ],
+ "aliases_ascii": [],
+ "keywords": [
+ "nature",
+ "weather"
+ ]
+ },
"wind_blowing_face": {
"unicode": "1F32C",
"unicode_alternates": [],
@@ -13174,7 +29288,10 @@
"category": "nature",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["mother", "nature"]
+ "keywords": [
+ "mother",
+ "nature"
+ ]
},
"wind_chime": {
"unicode": "1F390",
@@ -13184,7 +29301,21 @@
"category": "objects",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["ding", "nature", "wind", "chime", "bell", "fūrin", "instrument", "music", "spirits", "soothing", "protective", "spiritual", "sound"],
+ "keywords": [
+ "ding",
+ "nature",
+ "wind",
+ "chime",
+ "bell",
+ "fūrin",
+ "instrument",
+ "music",
+ "spirits",
+ "soothing",
+ "protective",
+ "spiritual",
+ "sound"
+ ],
"moji": "🎐"
},
"wine_glass": {
@@ -13195,7 +29326,20 @@
"category": "objects",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["alcohol", "beverage", "booze", "bottle", "drink", "drunk", "fermented", "glass", "grapes", "tasting", "wine", "winery"],
+ "keywords": [
+ "alcohol",
+ "beverage",
+ "booze",
+ "bottle",
+ "drink",
+ "drunk",
+ "fermented",
+ "glass",
+ "grapes",
+ "tasting",
+ "wine",
+ "winery"
+ ],
"moji": "🍷"
},
"wink": {
@@ -13205,8 +29349,26 @@
"shortname": ":wink:",
"category": "emoticons",
"aliases": [],
- "aliases_ascii": [";)", ";-)", "*-)", "*)", ";-]", ";]", ";D", ";^)"],
- "keywords": ["face", "happy", "mischievous", "secret", "wink", "winking", "friendly", "joke"],
+ "aliases_ascii": [
+ ";)",
+ ";-)",
+ "*-)",
+ "*)",
+ ";-]",
+ ";]",
+ ";D",
+ ";^)"
+ ],
+ "keywords": [
+ "face",
+ "happy",
+ "mischievous",
+ "secret",
+ "wink",
+ "winking",
+ "friendly",
+ "joke"
+ ],
"moji": "😉"
},
"wolf": {
@@ -13217,7 +29379,10 @@
"category": "nature",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["animal", "nature"],
+ "keywords": [
+ "animal",
+ "nature"
+ ],
"moji": "🐺"
},
"woman": {
@@ -13228,9 +29393,82 @@
"category": "emoticons",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["female", "girls"],
+ "keywords": [
+ "female",
+ "girls"
+ ],
"moji": "👩"
},
+ "woman_tone1": {
+ "unicode": "1F469-1F3FB",
+ "unicode_alternates": "",
+ "name": "woman tone 1",
+ "shortname": ":woman_tone1:",
+ "category": "people",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": [
+ "female",
+ "girl",
+ "lady"
+ ]
+ },
+ "woman_tone2": {
+ "unicode": "1F469-1F3FC",
+ "unicode_alternates": "",
+ "name": "woman tone 2",
+ "shortname": ":woman_tone2:",
+ "category": "people",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": [
+ "female",
+ "girl",
+ "lady"
+ ]
+ },
+ "woman_tone3": {
+ "unicode": "1F469-1F3FD",
+ "unicode_alternates": "",
+ "name": "woman tone 3",
+ "shortname": ":woman_tone3:",
+ "category": "people",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": [
+ "female",
+ "girl",
+ "lady"
+ ]
+ },
+ "woman_tone4": {
+ "unicode": "1F469-1F3FE",
+ "unicode_alternates": "",
+ "name": "woman tone 4",
+ "shortname": ":woman_tone4:",
+ "category": "people",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": [
+ "female",
+ "girl",
+ "lady"
+ ]
+ },
+ "woman_tone5": {
+ "unicode": "1F469-1F3FF",
+ "unicode_alternates": "",
+ "name": "woman tone 5",
+ "shortname": ":woman_tone5:",
+ "category": "people",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": [
+ "female",
+ "girl",
+ "lady"
+ ]
+ },
"womans_clothes": {
"unicode": "1F45A",
"unicode_alternates": [],
@@ -13239,7 +29477,21 @@
"category": "emoticons",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["fashion", "woman", "clothing", "clothes", "blouse", "shirt", "wardrobe", "breasts", "cleavage", "shopping", "shop", "dressing", "dressed"],
+ "keywords": [
+ "fashion",
+ "woman",
+ "clothing",
+ "clothes",
+ "blouse",
+ "shirt",
+ "wardrobe",
+ "breasts",
+ "cleavage",
+ "shopping",
+ "shop",
+ "dressing",
+ "dressed"
+ ],
"moji": "👚"
},
"womans_hat": {
@@ -13250,7 +29502,11 @@
"category": "emoticons",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["accessories", "fashion", "female"],
+ "keywords": [
+ "accessories",
+ "fashion",
+ "female"
+ ],
"moji": "👒"
},
"womens": {
@@ -13261,7 +29517,16 @@
"category": "other",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["purple-square", "woman", "bathroom", "restroom", "sign", "girl", "female", "avatar"],
+ "keywords": [
+ "purple-square",
+ "woman",
+ "bathroom",
+ "restroom",
+ "sign",
+ "girl",
+ "female",
+ "avatar"
+ ],
"moji": "🚺"
},
"worried": {
@@ -13272,7 +29537,16 @@
"category": "emoticons",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["concern", "face", "nervous", "worried", "anxious", "distressed", "nervous", "tense"],
+ "keywords": [
+ "concern",
+ "face",
+ "nervous",
+ "worried",
+ "anxious",
+ "distressed",
+ "nervous",
+ "tense"
+ ],
"moji": "😟"
},
"wrench": {
@@ -13283,7 +29557,11 @@
"category": "objects",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["diy", "ikea", "tools"],
+ "keywords": [
+ "diy",
+ "ikea",
+ "tools"
+ ],
"moji": "🔧"
},
"writing_hand": {
@@ -13292,9 +29570,91 @@
"name": "left writing hand",
"shortname": ":writing_hand:",
"category": "people",
- "aliases": [":left_writing_hand:"],
+ "aliases": [
+ ":left_writing_hand:"
+ ],
+ "aliases_ascii": [],
+ "keywords": [
+ "write",
+ "sign",
+ "signature",
+ "draw"
+ ]
+ },
+ "writing_hand_tone1": {
+ "unicode": "270D-1F3FB",
+ "unicode_alternates": "",
+ "name": "writing hand tone 1",
+ "shortname": ":writing_hand_tone1:",
+ "category": "people",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": [
+ "write",
+ "sign",
+ "signature",
+ "draw"
+ ]
+ },
+ "writing_hand_tone2": {
+ "unicode": "270D-1F3FC",
+ "unicode_alternates": "",
+ "name": "writing hand tone 2",
+ "shortname": ":writing_hand_tone2:",
+ "category": "people",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": [
+ "write",
+ "sign",
+ "signature",
+ "draw"
+ ]
+ },
+ "writing_hand_tone3": {
+ "unicode": "270D-1F3FD",
+ "unicode_alternates": "",
+ "name": "writing hand tone 3",
+ "shortname": ":writing_hand_tone3:",
+ "category": "people",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": [
+ "write",
+ "sign",
+ "signature",
+ "draw"
+ ]
+ },
+ "writing_hand_tone4": {
+ "unicode": "270D-1F3FE",
+ "unicode_alternates": "",
+ "name": "writing hand tone 4",
+ "shortname": ":writing_hand_tone4:",
+ "category": "people",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": [
+ "write",
+ "sign",
+ "signature",
+ "draw"
+ ]
+ },
+ "writing_hand_tone5": {
+ "unicode": "270D-1F3FF",
+ "unicode_alternates": "",
+ "name": "writing hand tone 5",
+ "shortname": ":writing_hand_tone5:",
+ "category": "people",
+ "aliases": [],
"aliases_ascii": [],
- "keywords": ["write", "sign", "signature", "draw"]
+ "keywords": [
+ "write",
+ "sign",
+ "signature",
+ "draw"
+ ]
},
"x": {
"unicode": "274C",
@@ -13304,7 +29664,11 @@
"category": "other",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["delete", "no", "remove"],
+ "keywords": [
+ "delete",
+ "no",
+ "remove"
+ ],
"moji": "❌"
},
"yellow_heart": {
@@ -13315,7 +29679,25 @@
"category": "emoticons",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["affection", "like", "love", "valentines", "yellow", "gold", "heart", "love", "friendship", "happy", "happiness", "trust", "compassionate", "respectful", "honest", "caring", "selfless"],
+ "keywords": [
+ "affection",
+ "like",
+ "love",
+ "valentines",
+ "yellow",
+ "gold",
+ "heart",
+ "love",
+ "friendship",
+ "happy",
+ "happiness",
+ "trust",
+ "compassionate",
+ "respectful",
+ "honest",
+ "caring",
+ "selfless"
+ ],
"moji": "💛"
},
"yen": {
@@ -13326,9 +29708,39 @@
"category": "objects",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["currency", "dollar", "japanese", "money", "yen", "japan", "japanese", "banknote", "money", "currency", "paper", "cash", "bill"],
+ "keywords": [
+ "currency",
+ "dollar",
+ "japanese",
+ "money",
+ "yen",
+ "japan",
+ "japanese",
+ "banknote",
+ "money",
+ "currency",
+ "paper",
+ "cash",
+ "bill"
+ ],
"moji": "💴"
},
+ "yin_yang": {
+ "unicode": "262F",
+ "unicode_alternates": "",
+ "name": "yin yang",
+ "shortname": ":yin_yang:",
+ "category": "symbols",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": [
+ "religion",
+ "sign",
+ "symbol",
+ "tao",
+ "taoist"
+ ]
+ },
"yum": {
"unicode": "1F60B",
"unicode_alternates": [],
@@ -13337,30 +29749,68 @@
"category": "emoticons",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["face", "happy", "joy", "smile", "tongue", "delicious", "savoring", "food", "eat", "yummy", "yum", "tasty", "savory"],
+ "keywords": [
+ "face",
+ "happy",
+ "joy",
+ "smile",
+ "tongue",
+ "delicious",
+ "savoring",
+ "food",
+ "eat",
+ "yummy",
+ "yum",
+ "tasty",
+ "savory"
+ ],
"moji": "😋"
},
"zap": {
"unicode": "26A1",
- "unicode_alternates": ["26A1-FE0F"],
+ "unicode_alternates": [
+ "26A1-FE0F"
+ ],
"name": "high voltage sign",
"shortname": ":zap:",
"category": "nature",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["lightning bolt", "thunder", "weather"],
+ "keywords": [
+ "lightning bolt",
+ "thunder",
+ "weather"
+ ],
"moji": "⚡"
},
"zero": {
"moji": "0️⃣",
"unicode": "0030-20E3",
- "unicode_alternates": ["0030-FE0F-20E3"],
+ "unicode_alternates": [
+ "0030-FE0F-20E3"
+ ],
"name": "digit zero",
"shortname": ":zero:",
"category": "other",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["blue-square", "null", "numbers"]
+ "keywords": [
+ "blue-square",
+ "null",
+ "numbers"
+ ]
+ },
+ "zipper_mouth": {
+ "unicode": "1F910",
+ "unicode_alternates": "",
+ "name": "zipper-mouth face",
+ "shortname": ":zipper_mouth:",
+ "category": "people",
+ "aliases": [
+ ":zipper_mouth_face:"
+ ],
+ "aliases_ascii": [],
+ "keywords": []
},
"zzz": {
"unicode": "1F4A4",
@@ -13370,7 +29820,10 @@
"category": "emoticons",
"aliases": [],
"aliases_ascii": [],
- "keywords": ["sleepy", "tired"],
+ "keywords": [
+ "sleepy",
+ "tired"
+ ],
"moji": "💤"
}
}
diff --git a/lib/api/api.rb b/lib/api/api.rb
index 7efe0a0262f..7d65145176b 100644
--- a/lib/api/api.rb
+++ b/lib/api/api.rb
@@ -56,5 +56,6 @@ module API
mount Triggers
mount Builds
mount Variables
+ mount Runners
end
end
diff --git a/lib/api/builds.rb b/lib/api/builds.rb
index a8bd3842ce4..2b104f90aa7 100644
--- a/lib/api/builds.rb
+++ b/lib/api/builds.rb
@@ -60,6 +60,30 @@ module API
user_can_download_artifacts: can?(current_user, :read_build, user_project)
end
+ # Download the artifacts file from build
+ #
+ # Parameters:
+ # id (required) - The ID of a build
+ # token (required) - The build authorization token
+ # Example Request:
+ # GET /projects/:id/builds/:build_id/artifacts
+ get ':id/builds/:build_id/artifacts' do
+ authorize_read_builds!
+
+ build = get_build(params[:build_id])
+ return not_found!(build) unless build
+
+ artifacts_file = build.artifacts_file
+
+ unless artifacts_file.file_storage?
+ return redirect_to build.artifacts_file.url
+ end
+
+ return not_found! unless artifacts_file.exists?
+
+ present_file!(artifacts_file.path, artifacts_file.filename)
+ end
+
# Get a trace of a specific build of a project
#
# Parameters:
@@ -115,13 +139,33 @@ module API
authorize_update_builds!
build = get_build(params[:build_id])
- return forbidden!('Build is not retryable') unless build && build.retryable?
+ return not_found!(build) unless build
+ return forbidden!('Build is not retryable') unless build.retryable?
build = Ci::Build.retry(build)
present build, with: Entities::Build,
user_can_download_artifacts: can?(current_user, :read_build, user_project)
end
+
+ # Erase build (remove artifacts and build trace)
+ #
+ # Parameters:
+ # id (required) - the id of a project
+ # build_id (required) - the id of a build
+ # example Request:
+ # post /projects/:id/build/:build_id/erase
+ post ':id/builds/:build_id/erase' do
+ authorize_update_builds!
+
+ build = get_build(params[:build_id])
+ return not_found!(build) unless build
+ return forbidden!('Build is not erasable!') unless build.erasable?
+
+ build.erase(erased_by: current_user)
+ present build, with: Entities::Build,
+ user_can_download_artifacts: can?(current_user, :download_build_artifacts, user_project)
+ end
end
helpers do
diff --git a/lib/api/commit_statuses.rb b/lib/api/commit_statuses.rb
index 9422d438d21..8e74e177ea0 100644
--- a/lib/api/commit_statuses.rb
+++ b/lib/api/commit_statuses.rb
@@ -18,10 +18,12 @@ module API
# Examples:
# GET /projects/:id/repository/commits/:sha/statuses
get ':id/repository/commits/:sha/statuses' do
- authorize! :read_commit_status, user_project
- sha = params[:sha]
- ci_commit = user_project.ci_commit(sha)
- not_found! 'Commit' unless ci_commit
+ authorize!(:read_commit_status, user_project)
+
+ not_found!('Commit') unless user_project.commit(params[:sha])
+ ci_commit = user_project.ci_commit(params[:sha])
+ return [] unless ci_commit
+
statuses = ci_commit.statuses
statuses = statuses.latest unless parse_boolean(params[:all])
statuses = statuses.where(ref: params[:ref]) if params[:ref].present?
diff --git a/lib/api/commits.rb b/lib/api/commits.rb
index f4efb651eb6..4544a41b1e3 100644
--- a/lib/api/commits.rb
+++ b/lib/api/commits.rb
@@ -48,7 +48,7 @@ module API
sha = params[:sha]
commit = user_project.commit(sha)
not_found! "Commit" unless commit
- commit.diffs
+ commit.diffs.to_a
end
# Get a commit's comments
@@ -90,9 +90,9 @@ module API
}
if params[:path] && params[:line] && params[:line_type]
- commit.diffs.each do |diff|
+ commit.diffs(all_diffs: true).each do |diff|
next unless diff.new_path == params[:path]
- lines = Gitlab::Diff::Parser.new.parse(diff.diff.lines.to_a)
+ lines = Gitlab::Diff::Parser.new.parse(diff.diff.each_line)
lines.each do |line|
next unless line.new_pos == params[:line].to_i && line.type == params[:line_type]
diff --git a/lib/api/entities.rb b/lib/api/entities.rb
index a9c09ffdb31..b021db8fa5b 100644
--- a/lib/api/entities.rb
+++ b/lib/api/entities.rb
@@ -49,7 +49,7 @@ module API
expose :enable_ssl_verification
end
- class ForkedFromProject < Grape::Entity
+ class BasicProjectDetails < Grape::Entity
expose :id
expose :name, :name_with_namespace
expose :path, :path_with_namespace
@@ -67,7 +67,7 @@ module API
expose :shared_runners_enabled
expose :creator_id
expose :namespace
- expose :forked_from_project, using: Entities::ForkedFromProject, if: lambda{ |project, options| project.forked? }
+ expose :forked_from_project, using: Entities::BasicProjectDetails, if: lambda{ |project, options| project.forked? }
expose :avatar_url
expose :star_count, :forks_count
expose :open_issues_count, if: lambda { |project, options| project.issues_enabled? && project.default_issues_tracker? }
@@ -181,7 +181,7 @@ module API
class MergeRequestChanges < MergeRequest
expose :diffs, as: :changes, using: Entities::RepoDiff do |compare, _|
- compare.diffs
+ compare.diffs(all_diffs: true).to_a
end
end
@@ -295,11 +295,11 @@ module API
end
expose :diffs, using: Entities::RepoDiff do |compare, options|
- compare.diffs
+ compare.diffs(all_diffs: true).to_a
end
expose :compare_timeout do |compare, options|
- compare.timeout
+ compare.diffs.overflow?
end
expose :same, as: :compare_same_ref
@@ -377,6 +377,24 @@ module API
expose :name
end
+ class RunnerDetails < Runner
+ expose :tag_list
+ expose :version, :revision, :platform, :architecture
+ expose :contacted_at
+ expose :token, if: lambda { |runner, options| options[:current_user].is_admin? || !runner.is_shared? }
+ expose :projects, with: Entities::BasicProjectDetails do |runner, options|
+ if options[:current_user].is_admin?
+ runner.projects
+ else
+ options[:current_user].authorized_projects.where(id: runner.projects)
+ end
+ end
+ end
+
+ class BuildArtifactFile < Grape::Entity
+ expose :filename, :size
+ end
+
class Build < Grape::Entity
expose :id, :status, :stage, :name, :ref, :tag, :coverage
expose :created_at, :started_at, :finished_at
@@ -388,6 +406,7 @@ module API
repo_obj.artifacts_download_url
end
end
+ expose :artifacts_file, using: BuildArtifactFile, if: -> (build, opts) { build.artifacts? }
expose :commit, with: RepoCommit do |repo_obj, _options|
if repo_obj.respond_to?(:commit)
repo_obj.commit.commit_data
diff --git a/lib/api/internal.rb b/lib/api/internal.rb
index e38736fc28b..2200208b946 100644
--- a/lib/api/internal.rb
+++ b/lib/api/internal.rb
@@ -14,6 +14,14 @@ module API
# ref - branch name
# forced_push - forced_push
#
+
+ helpers do
+ def wiki?
+ @wiki ||= params[:project].end_with?('.wiki') &&
+ !Project.find_with_namespace(params[:project])
+ end
+ end
+
post "/allowed" do
status 200
@@ -30,13 +38,12 @@ module API
# Strip out the .wiki from the pathname before finding the
# project. This applies the correct project permissions to
# the wiki repository as well.
- wiki = project_path.end_with?('.wiki')
- project_path.chomp!('.wiki') if wiki
+ project_path.chomp!('.wiki') if wiki?
project = Project.find_with_namespace(project_path)
access =
- if wiki
+ if wiki?
Gitlab::GitAccessWiki.new(actor, project)
else
Gitlab::GitAccess.new(actor, project)
diff --git a/lib/api/repositories.rb b/lib/api/repositories.rb
index c95d2d2001d..0d0f0d4616d 100644
--- a/lib/api/repositories.rb
+++ b/lib/api/repositories.rb
@@ -98,11 +98,8 @@ module API
authorize! :download_code, user_project
begin
- ArchiveRepositoryService.new(
- user_project,
- params[:sha],
- params[:format]
- ).execute
+ RepositoryArchiveCacheWorker.perform_async
+ header *Gitlab::Workhorse.send_git_archive(user_project, params[:sha], params[:format])
rescue
not_found!('File')
end
diff --git a/lib/api/runners.rb b/lib/api/runners.rb
new file mode 100644
index 00000000000..8ec91485b26
--- /dev/null
+++ b/lib/api/runners.rb
@@ -0,0 +1,175 @@
+module API
+ # Runners API
+ class Runners < Grape::API
+ before { authenticate! }
+
+ resource :runners do
+ # Get runners available for user
+ #
+ # Example Request:
+ # GET /runners
+ get do
+ runners = filter_runners(current_user.ci_authorized_runners, params[:scope], without: ['specific', 'shared'])
+ present paginate(runners), with: Entities::Runner
+ end
+
+ # Get all runners - shared and specific
+ #
+ # Example Request:
+ # GET /runners/all
+ get 'all' do
+ authenticated_as_admin!
+ runners = filter_runners(Ci::Runner.all, params[:scope])
+ present paginate(runners), with: Entities::Runner
+ end
+
+ # Get runner's details
+ #
+ # Parameters:
+ # id (required) - The ID of ther runner
+ # Example Request:
+ # GET /runners/:id
+ get ':id' do
+ runner = get_runner(params[:id])
+ authenticate_show_runner!(runner)
+
+ present runner, with: Entities::RunnerDetails, current_user: current_user
+ end
+
+ # Update runner's details
+ #
+ # Parameters:
+ # id (required) - The ID of ther runner
+ # description (optional) - Runner's description
+ # active (optional) - Runner's status
+ # tag_list (optional) - Array of tags for runner
+ # Example Request:
+ # PUT /runners/:id
+ put ':id' do
+ runner = get_runner(params[:id])
+ authenticate_update_runner!(runner)
+
+ attrs = attributes_for_keys [:description, :active, :tag_list]
+ if runner.update(attrs)
+ present runner, with: Entities::RunnerDetails, current_user: current_user
+ else
+ render_validation_error!(runner)
+ end
+ end
+
+ # Remove runner
+ #
+ # Parameters:
+ # id (required) - The ID of ther runner
+ # Example Request:
+ # DELETE /runners/:id
+ delete ':id' do
+ runner = get_runner(params[:id])
+ authenticate_delete_runner!(runner)
+ runner.destroy!
+
+ present runner, with: Entities::Runner
+ end
+ end
+
+ resource :projects do
+ before { authorize_admin_project }
+
+ # Get runners available for project
+ #
+ # Example Request:
+ # GET /projects/:id/runners
+ get ':id/runners' do
+ runners = filter_runners(Ci::Runner.owned_or_shared(user_project.id), params[:scope])
+ present paginate(runners), with: Entities::Runner
+ end
+
+ # Enable runner for project
+ #
+ # Parameters:
+ # id (required) - The ID of the project
+ # runner_id (required) - The ID of the runner
+ # Example Request:
+ # POST /projects/:id/runners/:runner_id
+ post ':id/runners' do
+ required_attributes! [:runner_id]
+
+ runner = get_runner(params[:runner_id])
+ authenticate_enable_runner!(runner)
+ Ci::RunnerProject.create(runner: runner, project: user_project)
+
+ present runner, with: Entities::Runner
+ end
+
+ # Disable project's runner
+ #
+ # Parameters:
+ # id (required) - The ID of the project
+ # runner_id (required) - The ID of the runner
+ # Example Request:
+ # DELETE /projects/:id/runners/:runner_id
+ delete ':id/runners/:runner_id' do
+ runner_project = user_project.runner_projects.find_by(runner_id: params[:runner_id])
+ not_found!('Runner') unless runner_project
+
+ runner = runner_project.runner
+ forbidden!("Only one project associated with the runner. Please remove the runner instead") if runner.projects.count == 1
+
+ runner_project.destroy
+
+ present runner, with: Entities::Runner
+ end
+ end
+
+ helpers do
+ def filter_runners(runners, scope, options = {})
+ return runners unless scope.present?
+
+ available_scopes = ::Ci::Runner::AVAILABLE_SCOPES
+ if options[:without]
+ available_scopes = available_scopes - options[:without]
+ end
+
+ if (available_scopes & [scope]).empty?
+ render_api_error!('Scope contains invalid value', 400)
+ end
+
+ runners.send(scope)
+ end
+
+ def get_runner(id)
+ runner = Ci::Runner.find(id)
+ not_found!('Runner') unless runner
+ runner
+ end
+
+ def authenticate_show_runner!(runner)
+ return if runner.is_shared || current_user.is_admin?
+ forbidden!("No access granted") unless user_can_access_runner?(runner)
+ end
+
+ def authenticate_update_runner!(runner)
+ return if current_user.is_admin?
+ forbidden!("Runner is shared") if runner.is_shared?
+ forbidden!("No access granted") unless user_can_access_runner?(runner)
+ end
+
+ def authenticate_delete_runner!(runner)
+ return if current_user.is_admin?
+ forbidden!("Runner is shared") if runner.is_shared?
+ forbidden!("Runner associated with more than one project") if runner.projects.count > 1
+ forbidden!("No access granted") unless user_can_access_runner?(runner)
+ end
+
+ def authenticate_enable_runner!(runner)
+ forbidden!("Runner is shared") if runner.is_shared?
+ return if current_user.is_admin?
+ forbidden!("No access granted") unless user_can_access_runner?(runner)
+ end
+
+ def user_can_access_runner?(runner)
+ current_user.ci_authorized_runners.exists?(runner.id)
+ end
+ end
+ end
+end
diff --git a/lib/banzai.rb b/lib/banzai.rb
index 093382261ae..b467413a7dd 100644
--- a/lib/banzai.rb
+++ b/lib/banzai.rb
@@ -7,6 +7,10 @@ module Banzai
Renderer.render_result(text, context)
end
+ def self.pre_process(text, context)
+ Renderer.pre_process(text, context)
+ end
+
def self.post_process(html, context)
Renderer.post_process(html, context)
end
diff --git a/lib/banzai/filter/abstract_reference_filter.rb b/lib/banzai/filter/abstract_reference_filter.rb
index cdbaecf8d90..34c38913474 100644
--- a/lib/banzai/filter/abstract_reference_filter.rb
+++ b/lib/banzai/filter/abstract_reference_filter.rb
@@ -94,6 +94,8 @@ module Banzai
object_link_filter(link, object_class.link_reference_pattern, link_text: text)
end
end
+
+ doc
end
# Replace references (like `!123` for merge requests) in text with links
diff --git a/lib/banzai/filter/emoji_filter.rb b/lib/banzai/filter/emoji_filter.rb
index 5952a031626..207437ba7cf 100644
--- a/lib/banzai/filter/emoji_filter.rb
+++ b/lib/banzai/filter/emoji_filter.rb
@@ -45,7 +45,8 @@ module Banzai
private
def emoji_url(name)
- emoji_path = "emoji/#{emoji_filename(name)}"
+ emoji_path = emoji_filename(name)
+
if context[:asset_host]
# Asset host is specified.
url_to_image(emoji_path)
diff --git a/lib/banzai/filter/gollum_tags_filter.rb b/lib/banzai/filter/gollum_tags_filter.rb
index fe01dae4850..f31f921903b 100644
--- a/lib/banzai/filter/gollum_tags_filter.rb
+++ b/lib/banzai/filter/gollum_tags_filter.rb
@@ -26,6 +26,10 @@ module Banzai
# * [[http://example.com/images/logo.png]]
# * [[http://example.com/images/logo.png|alt=Logo]]
#
+ # - Insert a Table of Contents list:
+ #
+ # * [[_TOC_]]
+ #
# Based on Gollum::Filter::Tags
#
# Context options:
@@ -50,21 +54,28 @@ module Banzai
# See https://github.com/gollum/gollum/wiki
#
# Rubular: http://rubular.com/r/7dQnE5CUCH
- TAGS_PATTERN = %r{\[\[(.+?)\]\]}
+ TAGS_PATTERN = %r{\[\[(.+?)\]\]}.freeze
# Pattern to match allowed image extensions
- ALLOWED_IMAGE_EXTENSIONS = %r{.+(jpg|png|gif|svg|bmp)\z}i
+ ALLOWED_IMAGE_EXTENSIONS = %r{.+(jpg|png|gif|svg|bmp)\z}i.freeze
def call
search_text_nodes(doc).each do |node|
- content = node.content
+ # A Gollum ToC tag is `[[_TOC_]]`, but due to MarkdownFilter running
+ # before this one, it will be converted into `[[<em>TOC</em>]]`, so it
+ # needs special-case handling
+ if toc_tag?(node)
+ process_toc_tag(node)
+ else
+ content = node.content
- next unless content.match(TAGS_PATTERN)
+ next unless content =~ TAGS_PATTERN
- html = process_tag($1)
+ html = process_tag($1)
- if html && html != node.content
- node.replace(html)
+ if html && html != node.content
+ node.replace(html)
+ end
end
end
@@ -73,6 +84,12 @@ module Banzai
private
+ # Replace an entire `[[<em>TOC</em>]]` node with the result generated by
+ # TableOfContentsFilter
+ def process_toc_tag(node)
+ node.parent.parent.replace(result[:toc].presence || '')
+ end
+
# Process a single tag into its final HTML form.
#
# tag - The String tag contents (the stuff inside the double brackets).
@@ -108,6 +125,12 @@ module Banzai
end
end
+ def toc_tag?(node)
+ node.content == 'TOC' &&
+ node.parent.name == 'em' &&
+ node.parent.parent.text == '[[TOC]]'
+ end
+
def image?(path)
path =~ ALLOWED_IMAGE_EXTENSIONS
end
diff --git a/lib/banzai/filter/label_reference_filter.rb b/lib/banzai/filter/label_reference_filter.rb
index 95e7d209119..8147e5ed3c7 100644
--- a/lib/banzai/filter/label_reference_filter.rb
+++ b/lib/banzai/filter/label_reference_filter.rb
@@ -1,78 +1,47 @@
module Banzai
module Filter
# HTML filter that replaces label references with links.
- class LabelReferenceFilter < ReferenceFilter
- # Public: Find label references in text
- #
- # LabelReferenceFilter.references_in(text) do |match, id, name|
- # "<a href=...>#{Label.find(id)}</a>"
- # end
- #
- # text - String text to search.
- #
- # Yields the String match, an optional Integer label ID, and an optional
- # String label name.
- #
- # Returns a String replaced with the return of the block.
- def self.references_in(text)
- text.gsub(Label.reference_pattern) do |match|
- yield match, $~[:label_id].to_i, $~[:label_name]
- end
+ class LabelReferenceFilter < AbstractReferenceFilter
+ def self.object_class
+ Label
end
- def self.referenced_by(node)
- { label: LazyReference.new(Label, node.attr("data-label")) }
+ def find_object(project, id)
+ project.labels.find(id)
end
- def call
- replace_text_nodes_matching(Label.reference_pattern) do |content|
- label_link_filter(content)
- end
-
- replace_link_nodes_with_href(Label.reference_pattern) do |link, text|
- label_link_filter(link, link_text: text)
+ def self.references_in(text, pattern = Label.reference_pattern)
+ text.gsub(pattern) do |match|
+ yield match, $~[:label_id].to_i, $~[:label_name], $~[:project], $~
end
end
- # Replace label references in text with links to the label specified.
- #
- # text - String text to replace references in.
- #
- # Returns a String with label references replaced with links. All links
- # have `gfm` and `gfm-label` class names attached for styling.
- def label_link_filter(text, link_text: nil)
- project = context[:project]
-
- self.class.references_in(text) do |match, id, name|
- params = label_params(id, name)
-
- if label = project.labels.find_by(params)
- url = url_for_label(project, label)
- klass = reference_class(:label)
- data = data_attribute(
- original: link_text || match,
- project: project.id,
- label: label.id
- )
+ def references_in(text, pattern = Label.reference_pattern)
+ text.gsub(pattern) do |match|
+ project = project_from_ref($~[:project])
+ params = label_params($~[:label_id].to_i, $~[:label_name])
+ label = project.labels.find_by(params)
- text = link_text || render_colored_label(label)
-
- %(<a href="#{url}" #{data}
- class="#{klass}">#{escape_once(text)}</a>)
+ if label
+ yield match, label.id, $~[:project], $~
else
match
end
end
end
- def url_for_label(project, label)
+ def url_for_object(label, project)
h = Gitlab::Application.routes.url_helpers
- h.namespace_project_issues_url( project.namespace, project, label_name: label.name,
- only_path: context[:only_path])
+ h.namespace_project_issues_url(project.namespace, project, label_name: label.name,
+ only_path: context[:only_path])
end
- def render_colored_label(label)
- LabelsHelper.render_colored_label(label)
+ def object_link_text(object, matches)
+ if context[:project] == object.project
+ LabelsHelper.render_colored_label(object)
+ else
+ LabelsHelper.render_colored_cross_project_label(object)
+ end
end
# Parameters to pass to `Label.find_by` based on the given arguments
diff --git a/lib/banzai/filter/sanitization_filter.rb b/lib/banzai/filter/sanitization_filter.rb
index 04ddfe53ed6..abd79b329ae 100644
--- a/lib/banzai/filter/sanitization_filter.rb
+++ b/lib/banzai/filter/sanitization_filter.rb
@@ -7,6 +7,8 @@ module Banzai
#
# Extends HTML::Pipeline::SanitizationFilter with a custom whitelist.
class SanitizationFilter < HTML::Pipeline::SanitizationFilter
+ UNSAFE_PROTOCOLS = %w(javascript :javascript data vbscript).freeze
+
def whitelist
whitelist = super
@@ -43,8 +45,8 @@ module Banzai
# Allow any protocol in `a` elements...
whitelist[:protocols].delete('a')
- # ...but then remove links with the `javascript` protocol
- whitelist[:transformers].push(remove_javascript_links)
+ # ...but then remove links with unsafe protocols
+ whitelist[:transformers].push(remove_unsafe_links)
# Remove `rel` attribute from `a` elements
whitelist[:transformers].push(remove_rel)
@@ -55,14 +57,14 @@ module Banzai
whitelist
end
- def remove_javascript_links
+ def remove_unsafe_links
lambda do |env|
node = env[:node]
return unless node.name == 'a'
return unless node.has_attribute?('href')
- if node['href'].start_with?('javascript', ':javascript')
+ if node['href'].start_with?(*UNSAFE_PROTOCOLS)
node.remove_attribute('href')
end
end
diff --git a/lib/banzai/filter/yaml_front_matter_filter.rb b/lib/banzai/filter/yaml_front_matter_filter.rb
new file mode 100644
index 00000000000..e4e2f3f228d
--- /dev/null
+++ b/lib/banzai/filter/yaml_front_matter_filter.rb
@@ -0,0 +1,28 @@
+require 'html/pipeline/filter'
+require 'yaml'
+
+module Banzai
+ module Filter
+ class YamlFrontMatterFilter < HTML::Pipeline::Filter
+ DELIM = '---'.freeze
+
+ # Hat-tip to Middleman: https://git.io/v2e0z
+ PATTERN = %r{
+ \A(?:[^\r\n]*coding:[^\r\n]*\r?\n)?
+ (?<start>#{DELIM})[ ]*\r?\n
+ (?<frontmatter>.*?)[ ]*\r?\n?
+ ^(?<stop>#{DELIM})[ ]*\r?\n?
+ \r?\n?
+ (?<content>.*)
+ }mx.freeze
+
+ def call
+ match = PATTERN.match(html)
+
+ return html unless match
+
+ "```yaml\n#{match['frontmatter']}\n```\n\n#{match['content']}"
+ end
+ end
+ end
+end
diff --git a/lib/banzai/filter_array.rb b/lib/banzai/filter_array.rb
new file mode 100644
index 00000000000..77835a14027
--- /dev/null
+++ b/lib/banzai/filter_array.rb
@@ -0,0 +1,27 @@
+module Banzai
+ class FilterArray < Array
+ # Insert a value immediately after another value
+ #
+ # If the preceding value does not exist, the new value is added to the end
+ # of the Array.
+ def insert_after(after_value, value)
+ i = index(after_value) || length - 1
+
+ insert(i + 1, value)
+ end
+
+ # Insert a value immediately before another value
+ #
+ # If the succeeding value does not exist, the new value is added to the
+ # beginning of the Array.
+ def insert_before(before_value, value)
+ i = index(before_value) || -1
+
+ if i < 0
+ unshift(value)
+ else
+ insert(i, value)
+ end
+ end
+ end
+end
diff --git a/lib/banzai/pipeline/base_pipeline.rb b/lib/banzai/pipeline/base_pipeline.rb
index db5177db7b3..f60966c3c0f 100644
--- a/lib/banzai/pipeline/base_pipeline.rb
+++ b/lib/banzai/pipeline/base_pipeline.rb
@@ -4,7 +4,7 @@ module Banzai
module Pipeline
class BasePipeline
def self.filters
- []
+ FilterArray[]
end
def self.transform_context(context)
diff --git a/lib/banzai/pipeline/broadcast_message_pipeline.rb b/lib/banzai/pipeline/broadcast_message_pipeline.rb
index 4bb85e24c38..adc09c8afbd 100644
--- a/lib/banzai/pipeline/broadcast_message_pipeline.rb
+++ b/lib/banzai/pipeline/broadcast_message_pipeline.rb
@@ -2,7 +2,7 @@ module Banzai
module Pipeline
class BroadcastMessagePipeline < DescriptionPipeline
def self.filters
- @filters ||= [
+ @filters ||= FilterArray[
Filter::MarkdownFilter,
Filter::SanitizationFilter,
diff --git a/lib/banzai/pipeline/combined_pipeline.rb b/lib/banzai/pipeline/combined_pipeline.rb
index 9485199132e..60190f8d9dd 100644
--- a/lib/banzai/pipeline/combined_pipeline.rb
+++ b/lib/banzai/pipeline/combined_pipeline.rb
@@ -10,7 +10,7 @@ module Banzai
end
def self.filters
- pipelines.flat_map(&:filters)
+ FilterArray.new(pipelines.flat_map(&:filters))
end
def self.transform_context(context)
diff --git a/lib/banzai/pipeline/gfm_pipeline.rb b/lib/banzai/pipeline/gfm_pipeline.rb
index b7a38ea8427..8cd4b50e65a 100644
--- a/lib/banzai/pipeline/gfm_pipeline.rb
+++ b/lib/banzai/pipeline/gfm_pipeline.rb
@@ -2,7 +2,7 @@ module Banzai
module Pipeline
class GfmPipeline < BasePipeline
def self.filters
- @filters ||= [
+ @filters ||= FilterArray[
Filter::SyntaxHighlightFilter,
Filter::SanitizationFilter,
diff --git a/lib/banzai/pipeline/plain_markdown_pipeline.rb b/lib/banzai/pipeline/plain_markdown_pipeline.rb
index 3fbc681457b..3f45db21869 100644
--- a/lib/banzai/pipeline/plain_markdown_pipeline.rb
+++ b/lib/banzai/pipeline/plain_markdown_pipeline.rb
@@ -2,7 +2,7 @@ module Banzai
module Pipeline
class PlainMarkdownPipeline < BasePipeline
def self.filters
- [
+ FilterArray[
Filter::MarkdownFilter
]
end
diff --git a/lib/banzai/pipeline/post_process_pipeline.rb b/lib/banzai/pipeline/post_process_pipeline.rb
index bd338c045f3..ecff094b1e5 100644
--- a/lib/banzai/pipeline/post_process_pipeline.rb
+++ b/lib/banzai/pipeline/post_process_pipeline.rb
@@ -2,7 +2,7 @@ module Banzai
module Pipeline
class PostProcessPipeline < BasePipeline
def self.filters
- [
+ FilterArray[
Filter::RelativeLinkFilter,
Filter::RedactorFilter
]
diff --git a/lib/banzai/pipeline/pre_process_pipeline.rb b/lib/banzai/pipeline/pre_process_pipeline.rb
new file mode 100644
index 00000000000..50dc978b452
--- /dev/null
+++ b/lib/banzai/pipeline/pre_process_pipeline.rb
@@ -0,0 +1,17 @@
+module Banzai
+ module Pipeline
+ class PreProcessPipeline < BasePipeline
+ def self.filters
+ FilterArray[
+ Filter::YamlFrontMatterFilter
+ ]
+ end
+
+ def self.transform_context(context)
+ context.merge(
+ pre_process: true
+ )
+ end
+ end
+ end
+end
diff --git a/lib/banzai/pipeline/reference_extraction_pipeline.rb b/lib/banzai/pipeline/reference_extraction_pipeline.rb
index eaddccd30a5..919998380e4 100644
--- a/lib/banzai/pipeline/reference_extraction_pipeline.rb
+++ b/lib/banzai/pipeline/reference_extraction_pipeline.rb
@@ -2,7 +2,7 @@ module Banzai
module Pipeline
class ReferenceExtractionPipeline < BasePipeline
def self.filters
- [
+ FilterArray[
Filter::ReferenceGathererFilter
]
end
diff --git a/lib/banzai/pipeline/single_line_pipeline.rb b/lib/banzai/pipeline/single_line_pipeline.rb
index 8b84ab401df..ba2555df98d 100644
--- a/lib/banzai/pipeline/single_line_pipeline.rb
+++ b/lib/banzai/pipeline/single_line_pipeline.rb
@@ -2,7 +2,7 @@ module Banzai
module Pipeline
class SingleLinePipeline < GfmPipeline
def self.filters
- @filters ||= [
+ @filters ||= FilterArray[
Filter::SanitizationFilter,
Filter::EmojiFilter,
diff --git a/lib/banzai/pipeline/wiki_pipeline.rb b/lib/banzai/pipeline/wiki_pipeline.rb
index 50b5450e70b..9b4ff0f0f80 100644
--- a/lib/banzai/pipeline/wiki_pipeline.rb
+++ b/lib/banzai/pipeline/wiki_pipeline.rb
@@ -4,7 +4,8 @@ module Banzai
module Pipeline
class WikiPipeline < FullPipeline
def self.filters
- super.insert(1, Filter::GollumTagsFilter)
+ @filters ||= super.insert_after(Filter::TableOfContentsFilter,
+ Filter::GollumTagsFilter)
end
end
end
diff --git a/lib/banzai/renderer.rb b/lib/banzai/renderer.rb
index 891c0fd7749..ae714c87dc5 100644
--- a/lib/banzai/renderer.rb
+++ b/lib/banzai/renderer.rb
@@ -31,6 +31,12 @@ module Banzai
Pipeline[context[:pipeline]].call(text, context)
end
+ def self.pre_process(text, context)
+ pipeline = Pipeline[:pre_process]
+
+ pipeline.to_html(text, context)
+ end
+
# Perform post-processing on an HTML String
#
# This method is used to perform state-dependent changes to a String of
diff --git a/lib/ci/api/builds.rb b/lib/ci/api/builds.rb
index 416b0b5f0b4..2e9a5d311f9 100644
--- a/lib/ci/api/builds.rb
+++ b/lib/ci/api/builds.rb
@@ -38,6 +38,8 @@ module Ci
authenticate_runner!
update_runner_last_contact
build = Ci::Build.where(runner_id: current_runner.id).running.find(params[:id])
+ forbidden!('Build has been erased!') if build.erased?
+
build.update_attributes(trace: params[:trace]) if params[:trace]
case params[:state].to_s
@@ -99,6 +101,7 @@ module Ci
not_found! unless build
authenticate_build_token!(build)
forbidden!('Build is not running!') unless build.running?
+ forbidden!('Build has been erased!') if build.erased?
artifacts_upload_path = ArtifactUploader.artifacts_upload_path
artifacts = uploaded_file(:file, artifacts_upload_path)
@@ -143,7 +146,7 @@ module Ci
present_file!(artifacts_file.path, artifacts_file.filename)
end
- # Remove the artifacts file from build
+ # Remove the artifacts file from build - Runners only
#
# Parameters:
# id (required) - The ID of a build
@@ -156,6 +159,7 @@ module Ci
build = Ci::Build.find_by_id(params[:id])
not_found! unless build
authenticate_build_token!(build)
+
build.remove_artifacts_file!
build.remove_artifacts_metadata!
end
diff --git a/lib/ci/status.rb b/lib/ci/status.rb
index c02b3b8f3e4..3fb1fe29494 100644
--- a/lib/ci/status.rb
+++ b/lib/ci/status.rb
@@ -1,11 +1,9 @@
module Ci
class Status
def self.get_status(statuses)
- statuses.reject! { |status| status.try(&:allow_failure?) }
-
if statuses.none?
'skipped'
- elsif statuses.all?(&:success?)
+ elsif statuses.all? { |status| status.success? || status.ignored? }
'success'
elsif statuses.all?(&:pending?)
'pending'
diff --git a/lib/gitlab/compare_result.rb b/lib/gitlab/compare_result.rb
deleted file mode 100644
index 0d696a1ee28..00000000000
--- a/lib/gitlab/compare_result.rb
+++ /dev/null
@@ -1,9 +0,0 @@
-module Gitlab
- class CompareResult
- attr_reader :commits, :diffs
-
- def initialize(compare, diff_options = {})
- @commits, @diffs = compare.commits, compare.diffs(nil, diff_options)
- end
- end
-end
diff --git a/lib/gitlab/database.rb b/lib/gitlab/database.rb
index 6ebb8027553..6f9da69983a 100644
--- a/lib/gitlab/database.rb
+++ b/lib/gitlab/database.rb
@@ -17,7 +17,7 @@ module Gitlab
end
def true_value
- if self.class.postgresql?
+ if Gitlab::Database.postgresql?
"'t'"
else
1
@@ -25,7 +25,7 @@ module Gitlab
end
def false_value
- if self.class.postgresql?
+ if Gitlab::Database.postgresql?
"'f'"
else
0
@@ -47,9 +47,5 @@ module Gitlab
row.first
end
end
-
- def connection
- self.class.connection
- end
end
end
diff --git a/lib/gitlab/diff/file.rb b/lib/gitlab/diff/file.rb
index a484177ae33..faa2830c16e 100644
--- a/lib/gitlab/diff/file.rb
+++ b/lib/gitlab/diff/file.rb
@@ -21,7 +21,7 @@ module Gitlab
# Array of Gitlab::DIff::Line objects
def diff_lines
- @lines ||= parser.parse(raw_diff.lines)
+ @lines ||= parser.parse(raw_diff.each_line).to_a
end
def highlighted_diff_lines
diff --git a/lib/gitlab/diff/parser.rb b/lib/gitlab/diff/parser.rb
index 3666063bf8b..d0f6ba23ab4 100644
--- a/lib/gitlab/diff/parser.rb
+++ b/lib/gitlab/diff/parser.rb
@@ -5,52 +5,53 @@ module Gitlab
def parse(lines)
@lines = lines
- lines_obj = []
line_obj_index = 0
line_old = 1
line_new = 1
type = nil
- @lines.each do |line|
- next if filename?(line)
-
- full_line = line.gsub(/\n/, '')
-
- if line.match(/^@@ -/)
- type = "match"
-
- line_old = line.match(/\-[0-9]*/)[0].to_i.abs rescue 0
- line_new = line.match(/\+[0-9]*/)[0].to_i.abs rescue 0
-
- next if line_old <= 1 && line_new <= 1 #top of file
- lines_obj << Gitlab::Diff::Line.new(full_line, type, line_obj_index, line_old, line_new)
- line_obj_index += 1
- next
- elsif line[0] == '\\'
- type = 'nonewline'
- lines_obj << Gitlab::Diff::Line.new(full_line, type, line_obj_index, line_old, line_new)
- line_obj_index += 1
- else
- type = identification_type(line)
- lines_obj << Gitlab::Diff::Line.new(full_line, type, line_obj_index, line_old, line_new)
- line_obj_index += 1
- end
-
-
- case line[0]
- when "+"
- line_new += 1
- when "-"
- line_old += 1
- when "\\"
- # No increment
- else
- line_new += 1
- line_old += 1
+ # By returning an Enumerator we make it possible to search for a single line (with #find)
+ # without having to instantiate all the others that come after it.
+ Enumerator.new do |yielder|
+ @lines.each do |line|
+ next if filename?(line)
+
+ full_line = line.gsub(/\n/, '')
+
+ if line.match(/^@@ -/)
+ type = "match"
+
+ line_old = line.match(/\-[0-9]*/)[0].to_i.abs rescue 0
+ line_new = line.match(/\+[0-9]*/)[0].to_i.abs rescue 0
+
+ next if line_old <= 1 && line_new <= 1 #top of file
+ yielder << Gitlab::Diff::Line.new(full_line, type, line_obj_index, line_old, line_new)
+ line_obj_index += 1
+ next
+ elsif line[0] == '\\'
+ type = 'nonewline'
+ yielder << Gitlab::Diff::Line.new(full_line, type, line_obj_index, line_old, line_new)
+ line_obj_index += 1
+ else
+ type = identification_type(line)
+ yielder << Gitlab::Diff::Line.new(full_line, type, line_obj_index, line_old, line_new)
+ line_obj_index += 1
+ end
+
+
+ case line[0]
+ when "+"
+ line_new += 1
+ when "-"
+ line_old += 1
+ when "\\"
+ # No increment
+ else
+ line_new += 1
+ line_old += 1
+ end
end
end
-
- lines_obj
end
def empty?
diff --git a/lib/gitlab/email/message/repository_push.rb b/lib/gitlab/email/message/repository_push.rb
index a05ffeb9cd2..41f0edcaf7e 100644
--- a/lib/gitlab/email/message/repository_push.rb
+++ b/lib/gitlab/email/message/repository_push.rb
@@ -50,7 +50,7 @@ module Gitlab
end
def compare_timeout
- compare.timeout if compare
+ diffs.overflow? if diffs
end
def reverse_compare?
diff --git a/lib/gitlab/gitlab_import/importer.rb b/lib/gitlab/gitlab_import/importer.rb
index 59926084d07..850b73244c6 100644
--- a/lib/gitlab/gitlab_import/importer.rb
+++ b/lib/gitlab/gitlab_import/importer.rb
@@ -12,7 +12,7 @@ module Gitlab
end
def execute
- project_identifier = CGI.escape(project.import_source, '/')
+ project_identifier = CGI.escape(project.import_source)
#Issues && Comments
issues = client.issues(project_identifier)
diff --git a/lib/gitlab/ldap/user.rb b/lib/gitlab/ldap/user.rb
index e044f0ecc6d..b84c81f1a6c 100644
--- a/lib/gitlab/ldap/user.rb
+++ b/lib/gitlab/ldap/user.rb
@@ -24,6 +24,10 @@ module Gitlab
update_user_attributes
end
+ def save
+ super('LDAP')
+ end
+
# instance methods
def gl_user
@gl_user ||= find_by_uid_and_provider || find_by_email || build_new_user
diff --git a/lib/gitlab/note_data_builder.rb b/lib/gitlab/note_data_builder.rb
index ea6b0ee796d..71cf6a0d886 100644
--- a/lib/gitlab/note_data_builder.rb
+++ b/lib/gitlab/note_data_builder.rb
@@ -53,13 +53,10 @@ module Gitlab
object_kind: "note",
user: user.hook_attrs,
project_id: project.id,
- repository: {
- name: project.name,
- url: project.url_to_repo,
- description: project.description,
- homepage: project.web_url,
- },
- object_attributes: note.hook_attrs
+ project: project.hook_attrs,
+ object_attributes: note.hook_attrs,
+ # DEPRECATED
+ repository: project.hook_attrs.slice(:name, :url, :description, :homepage)
}
base_data[:object_attributes][:url] =
diff --git a/lib/gitlab/o_auth/user.rb b/lib/gitlab/o_auth/user.rb
index d87a72f7ba3..832fb08a526 100644
--- a/lib/gitlab/o_auth/user.rb
+++ b/lib/gitlab/o_auth/user.rb
@@ -26,7 +26,7 @@ module Gitlab
gl_user.try(:valid?)
end
- def save
+ def save(provider = 'OAuth')
unauthorized_to_create unless gl_user
if needs_blocking?
@@ -36,10 +36,10 @@ module Gitlab
gl_user.save!
end
- log.info "(OAuth) saving user #{auth_hash.email} from login with extern_uid => #{auth_hash.uid}"
+ log.info "(#{provider}) saving user #{auth_hash.email} from login with extern_uid => #{auth_hash.uid}"
gl_user
rescue ActiveRecord::RecordInvalid => e
- log.info "(OAuth) Error saving user: #{gl_user.errors.full_messages}"
+ log.info "(#{provider}) Error saving user: #{gl_user.errors.full_messages}"
return self, e.record.errors
end
@@ -105,7 +105,12 @@ module Gitlab
end
def signup_enabled?
- Gitlab.config.omniauth.allow_single_sign_on
+ providers = Gitlab.config.omniauth.allow_single_sign_on
+ if providers.is_a?(Array)
+ providers.include?(auth_hash.provider)
+ else
+ providers
+ end
end
def block_after_signup?
diff --git a/lib/gitlab/push_data_builder.rb b/lib/gitlab/push_data_builder.rb
index 4f9cdef3869..da1c15fef61 100644
--- a/lib/gitlab/push_data_builder.rb
+++ b/lib/gitlab/push_data_builder.rb
@@ -22,6 +22,8 @@ module Gitlab
# }
#
def build(project, user, oldrev, newrev, ref, commits = [], message = nil)
+ commits = Array(commits)
+
# Total commits count
commits_count = commits.size
@@ -47,18 +49,14 @@ module Gitlab
user_id: user.id,
user_name: user.name,
user_email: user.email,
+ user_avatar: user.avatar_url,
project_id: project.id,
- repository: {
- name: project.name,
- url: project.url_to_repo,
- description: project.description,
- homepage: project.web_url,
- git_http_url: project.http_url_to_repo,
- git_ssh_url: project.ssh_url_to_repo,
- visibility_level: project.visibility_level
- },
+ project: project.hook_attrs,
commits: commit_attrs,
- total_commits_count: commits_count
+ total_commits_count: commits_count,
+ # DEPRECATED
+ repository: project.hook_attrs.slice(:name, :url, :description, :homepage,
+ :git_http_url, :git_ssh_url, :visibility_level)
}
data
diff --git a/lib/gitlab/saml/user.rb b/lib/gitlab/saml/user.rb
new file mode 100644
index 00000000000..b1e30110ef5
--- /dev/null
+++ b/lib/gitlab/saml/user.rb
@@ -0,0 +1,47 @@
+# SAML extension for User model
+#
+# * Find GitLab user based on SAML uid and provider
+# * Create new user from SAML data
+#
+module Gitlab
+ module Saml
+ class User < Gitlab::OAuth::User
+
+ def save
+ super('SAML')
+ end
+
+ def gl_user
+ @user ||= find_by_uid_and_provider
+
+ if auto_link_ldap_user?
+ @user ||= find_or_create_ldap_user
+ end
+
+ if auto_link_saml_enabled?
+ @user ||= find_by_email
+ end
+
+ if signup_enabled?
+ @user ||= build_new_user
+ end
+
+ @user
+ end
+
+ def find_by_email
+ if auth_hash.has_email?
+ user = ::User.find_by(email: auth_hash.email.downcase)
+ user.identities.new(extern_uid: auth_hash.uid, provider: auth_hash.provider) if user
+ user
+ end
+ end
+
+ protected
+
+ def auto_link_saml_enabled?
+ Gitlab.config.omniauth.auto_link_saml_user
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/workhorse.rb b/lib/gitlab/workhorse.rb
index a23120a4176..c3ddd4c2680 100644
--- a/lib/gitlab/workhorse.rb
+++ b/lib/gitlab/workhorse.rb
@@ -3,19 +3,38 @@ require 'json'
module Gitlab
class Workhorse
+ SEND_DATA_HEADER = 'Gitlab-Workhorse-Send-Data'
+
class << self
def send_git_blob(repository, blob)
- params_hash = {
+ params = {
'RepoPath' => repository.path_to_repo,
'BlobId' => blob.id,
}
- params = Base64.urlsafe_encode64(JSON.dump(params_hash))
[
- 'Gitlab-Workhorse-Send-Data',
- "git-blob:#{params}",
+ SEND_DATA_HEADER,
+ "git-blob:#{encode(params)}",
]
end
+
+ def send_git_archive(project, ref, format)
+ format ||= 'tar.gz'
+ format.downcase!
+ params = project.repository.archive_metadata(ref, Gitlab.config.gitlab.repository_downloads_path, format)
+ raise "Repository or ref not found" if params.empty?
+
+ [
+ SEND_DATA_HEADER,
+ "git-archive:#{encode(params)}",
+ ]
+ end
+
+ protected
+
+ def encode(hash)
+ Base64.urlsafe_encode64(JSON.dump(hash))
+ end
end
end
end
diff --git a/lib/tasks/brakeman.rake b/lib/tasks/brakeman.rake
index 5d4e0740373..d5a402907d8 100644
--- a/lib/tasks/brakeman.rake
+++ b/lib/tasks/brakeman.rake
@@ -2,7 +2,7 @@ desc 'Security check via brakeman'
task :brakeman do
# We get 0 warnings at level 'w3' but we would like to reach 'w2'. Merge
# requests are welcome!
- if system(*%W(brakeman --skip-files lib/backup/repository.rb -w3 -z))
+ if system(*%W(brakeman --no-progress --skip-files lib/backup/repository.rb -w3 -z))
puts 'Security check succeed'
else
puts 'Security check failed'
diff --git a/lib/tasks/cache.rake b/lib/tasks/cache.rake
index 1728dda72cf..f221afcf73a 100644
--- a/lib/tasks/cache.rake
+++ b/lib/tasks/cache.rake
@@ -1,11 +1,21 @@
namespace :cache do
+ CLEAR_BATCH_SIZE = 1000 # There seems to be no speedup when pushing beyond 1,000
+ REDIS_SCAN_START_STOP = '0' # Magic value, see http://redis.io/commands/scan
+
desc "GitLab | Clear redis cache"
task :clear => :environment do
- # Hack into Rails.cache until https://github.com/redis-store/redis-store/pull/225
- # is accepted (I hope) and we can update the redis-store gem.
redis_store = Rails.cache.instance_variable_get(:@data)
- redis_store.keys.each_slice(1000) do |key_slice|
- redis_store.del(*key_slice)
+ cursor = REDIS_SCAN_START_STOP
+ loop do
+ cursor, keys = redis_store.scan(
+ cursor,
+ match: "#{Gitlab::REDIS_CACHE_NAMESPACE}*",
+ count: CLEAR_BATCH_SIZE
+ )
+
+ redis_store.del(*keys) if keys.any?
+
+ break if cursor == REDIS_SCAN_START_STOP
end
end
end
diff --git a/lib/tasks/gitlab/check.rake b/lib/tasks/gitlab/check.rake
index 54d95cd62a5..581ab26db79 100644
--- a/lib/tasks/gitlab/check.rake
+++ b/lib/tasks/gitlab/check.rake
@@ -16,7 +16,6 @@ namespace :gitlab do
check_git_config
check_database_config_exists
- check_database_is_not_sqlite
check_migrations_are_up
check_orphaned_group_members
check_gitlab_config_exists
@@ -267,7 +266,7 @@ namespace :gitlab do
unless File.directory?(Rails.root.join('public/uploads'))
puts "no".red
try_fixing_it(
- "sudo -u #{gitlab_user} mkdir -m 750 #{Rails.root}/public/uploads"
+ "sudo -u #{gitlab_user} mkdir #{Rails.root}/public/uploads"
)
for_more_information(
see_installation_guide_section "GitLab"
@@ -279,21 +278,22 @@ namespace :gitlab do
upload_path = File.realpath(Rails.root.join('public/uploads'))
upload_path_tmp = File.join(upload_path, 'tmp')
- if File.stat(upload_path).mode == 040750
+ if File.stat(upload_path).mode == 040700
unless Dir.exists?(upload_path_tmp)
puts 'skipped (no tmp uploads folder yet)'.magenta
return
end
- # if tmp upload dir has incorrect permissions, assume others do as well
- if File.stat(upload_path_tmp).mode == 040755 && File.owned?(upload_path_tmp) # verify drwxr-xr-x permissions
+ # If tmp upload dir has incorrect permissions, assume others do as well
+ # Verify drwx------ permissions
+ if File.stat(upload_path_tmp).mode == 040700 && File.owned?(upload_path_tmp)
puts "yes".green
else
puts "no".red
try_fixing_it(
"sudo chown -R #{gitlab_user} #{upload_path}",
"sudo find #{upload_path} -type f -exec chmod 0644 {} \\;",
- "sudo find #{upload_path} -type d -not -path #{upload_path} -exec chmod 0755 {} \\;"
+ "sudo find #{upload_path} -type d -not -path #{upload_path} -exec chmod 0700 {} \\;"
)
for_more_information(
see_installation_guide_section "GitLab"
@@ -303,7 +303,7 @@ namespace :gitlab do
else
puts "no".red
try_fixing_it(
- "sudo chmod 0750 #{upload_path}",
+ "sudo find #{upload_path} -type d -not -path #{upload_path} -exec chmod 0700 {} \\;"
)
for_more_information(
see_installation_guide_section "GitLab"
@@ -728,13 +728,15 @@ namespace :gitlab do
def check_imap_authentication
print "IMAP server credentials are correct? ... "
- config = Gitlab.config.incoming_email
+ config_path = Rails.root.join('config', 'mail_room.yml')
+ config_file = YAML.load(ERB.new(File.read(config_path)).result)
+ config = config_file[:mailboxes].first
if config
begin
- imap = Net::IMAP.new(config.host, port: config.port, ssl: config.ssl)
- imap.starttls if config.start_tls
- imap.login(config.user, config.password)
+ imap = Net::IMAP.new(config[:host], port: config[:port], ssl: config[:ssl])
+ imap.starttls if config[:start_tls]
+ imap.login(config[:email], config[:password])
connected = true
rescue
connected = false
diff --git a/scripts/notify_slack.sh b/scripts/notify_slack.sh
new file mode 100755
index 00000000000..0a4239e132c
--- /dev/null
+++ b/scripts/notify_slack.sh
@@ -0,0 +1,13 @@
+#!/bin/bash
+# Sends Slack notification ERROR_MSG to CHANNEL
+# An env. variable CI_SLACK_WEBHOOK_URL needs to be set.
+
+CHANNEL=$1
+ERROR_MSG=$2
+
+if [ -z "$CHANNEL" ] || [ -z "$ERROR_MSG" ] || [ -z "$CI_SLACK_WEBHOOK_URL" ]; then
+ echo "Missing argument(s) - Use: $0 channel message"
+ echo "and set CI_SLACK_WEBHOOK_URL environment variable."
+else
+ curl -X POST --data-urlencode 'payload={"channel": "'"$CHANNEL"'", "username": "gitlab-ci", "text": "'"$ERROR_MSG"'", "icon_emoji": ":gitlab:"}' "$CI_SLACK_WEBHOOK_URL"
+fi \ No newline at end of file
diff --git a/spec/config/mail_room_spec.rb b/spec/config/mail_room_spec.rb
new file mode 100644
index 00000000000..462afb24f08
--- /dev/null
+++ b/spec/config/mail_room_spec.rb
@@ -0,0 +1,56 @@
+require "spec_helper"
+
+describe "mail_room.yml" do
+ let(:config_path) { "config/mail_room.yml" }
+ let(:configuration) { YAML.load(ERB.new(File.read(config_path)).result) }
+
+ context "when incoming email is disabled" do
+ before do
+ ENV["MAIL_ROOM_GITLAB_CONFIG_FILE"] = Rails.root.join("spec/fixtures/mail_room_disabled.yml").to_s
+ end
+
+ after do
+ ENV["MAIL_ROOM_GITLAB_CONFIG_FILE"] = nil
+ end
+
+ it "contains no configuration" do
+ expect(configuration[:mailboxes]).to be_nil
+ end
+ end
+
+ context "when incoming email is enabled" do
+ before do
+ ENV["MAIL_ROOM_GITLAB_CONFIG_FILE"] = Rails.root.join("spec/fixtures/mail_room_enabled.yml").to_s
+ end
+
+ after do
+ ENV["MAIL_ROOM_GITLAB_CONFIG_FILE"] = nil
+ end
+
+ it "contains the intended configuration" do
+ expect(configuration[:mailboxes].length).to eq(1)
+
+ mailbox = configuration[:mailboxes].first
+
+ expect(mailbox[:host]).to eq("imap.gmail.com")
+ expect(mailbox[:port]).to eq(993)
+ expect(mailbox[:ssl]).to eq(true)
+ expect(mailbox[:start_tls]).to eq(false)
+ expect(mailbox[:email]).to eq("gitlab-incoming@gmail.com")
+ expect(mailbox[:password]).to eq("[REDACTED]")
+ expect(mailbox[:name]).to eq("inbox")
+
+ redis_config_file = Rails.root.join('config', 'resque.yml')
+
+ redis_url =
+ if File.exists?(redis_config_file)
+ YAML.load_file(redis_config_file)[Rails.env]
+ else
+ "redis://localhost:6379"
+ end
+
+ expect(mailbox[:delivery_options][:redis_url]).to eq(redis_url)
+ expect(mailbox[:arbitration_options][:redis_url]).to eq(redis_url)
+ end
+ end
+end
diff --git a/spec/controllers/autocomplete_controller_spec.rb b/spec/controllers/autocomplete_controller_spec.rb
index 85379a8e984..410b993fdfb 100644
--- a/spec/controllers/autocomplete_controller_spec.rb
+++ b/spec/controllers/autocomplete_controller_spec.rb
@@ -21,7 +21,7 @@ describe AutocompleteController do
it { expect(body).to be_kind_of(Array) }
it { expect(body.size).to eq 1 }
- it { expect(body.first["username"]).to eq user.username }
+ it { expect(body.map { |u| u["username"] }).to include(user.username) }
end
describe 'GET #users with unknown project' do
diff --git a/spec/controllers/ci/projects_controller_spec.rb b/spec/controllers/ci/projects_controller_spec.rb
new file mode 100644
index 00000000000..db0748f323f
--- /dev/null
+++ b/spec/controllers/ci/projects_controller_spec.rb
@@ -0,0 +1,53 @@
+require 'spec_helper'
+
+describe Ci::ProjectsController do
+ let(:visibility) { :public }
+ let!(:project) { create(:project, visibility, ci_id: 1) }
+ let(:ci_id) { project.ci_id }
+
+ ##
+ # Specs for *deprecated* CI badge
+ #
+ describe '#badge' do
+ shared_examples 'badge provider' do
+ it 'shows badge' do
+ expect(response.status).to eq 200
+ expect(response.headers)
+ .to include('Content-Type' => 'image/svg+xml')
+ end
+ end
+
+ context 'user not signed in' do
+ before { get(:badge, id: ci_id) }
+
+ context 'project has no ci_id reference' do
+ let(:ci_id) { 123 }
+
+ it 'returns 404' do
+ expect(response.status).to eq 404
+ end
+ end
+
+ context 'project is public' do
+ let(:visibility) { :public }
+ it_behaves_like 'badge provider'
+ end
+
+ context 'project is private' do
+ let(:visibility) { :private }
+ it_behaves_like 'badge provider'
+ end
+ end
+
+ context 'user signed in' do
+ let(:user) { create(:user) }
+ before { sign_in(user) }
+ before { get(:badge, id: ci_id) }
+
+ context 'private is internal' do
+ let(:visibility) { :internal }
+ it_behaves_like 'badge provider'
+ end
+ end
+ end
+end
diff --git a/spec/controllers/commit_controller_spec.rb b/spec/controllers/commit_controller_spec.rb
index 7793bf1e421..f09e4fcb154 100644
--- a/spec/controllers/commit_controller_spec.rb
+++ b/spec/controllers/commit_controller_spec.rb
@@ -81,7 +81,7 @@ describe Projects::CommitController do
expect(response.body).to start_with("diff --git")
# without whitespace option, there are more than 2 diff_splits
- diff_splits = assigns(:diffs)[0].diff.split("\n")
+ diff_splits = assigns(:diffs).first.diff.split("\n")
expect(diff_splits.length).to be <= 2
end
end
@@ -143,4 +143,53 @@ describe Projects::CommitController do
expect(assigns(:tags)).to include("v1.1.0")
end
end
+
+ describe '#revert' do
+ context 'when target branch is not provided' do
+ it 'should render the 404 page' do
+ post(:revert,
+ namespace_id: project.namespace.to_param,
+ project_id: project.to_param,
+ id: commit.id)
+
+ expect(response).not_to be_success
+ expect(response.status).to eq(404)
+ end
+ end
+
+ context 'when the revert was successful' do
+ it 'should redirect to the commits page' do
+ post(:revert,
+ namespace_id: project.namespace.to_param,
+ project_id: project.to_param,
+ target_branch: 'master',
+ id: commit.id)
+
+ expect(response).to redirect_to namespace_project_commits_path(project.namespace, project, 'master')
+ expect(flash[:notice]).to eq('The commit has been successfully reverted.')
+ end
+ end
+
+ context 'when the revert failed' do
+ before do
+ post(:revert,
+ namespace_id: project.namespace.to_param,
+ project_id: project.to_param,
+ target_branch: 'master',
+ id: commit.id)
+ end
+
+ it 'should redirect to the commit page' do
+ # Reverting a commit that has been already reverted.
+ post(:revert,
+ namespace_id: project.namespace.to_param,
+ project_id: project.to_param,
+ target_branch: 'master',
+ id: commit.id)
+
+ expect(response).to redirect_to namespace_project_commit_path(project.namespace, project, commit.id)
+ expect(flash[:alert]).to match('Sorry, we cannot revert this commit automatically.')
+ end
+ end
+ end
end
diff --git a/spec/controllers/namespaces_controller_spec.rb b/spec/controllers/namespaces_controller_spec.rb
index 77436958711..d4a380cc2ee 100644
--- a/spec/controllers/namespaces_controller_spec.rb
+++ b/spec/controllers/namespaces_controller_spec.rb
@@ -1,7 +1,7 @@
require 'spec_helper'
describe NamespacesController do
- let!(:user) { create(:user, avatar: fixture_file_upload(Rails.root + "spec/fixtures/dk.png", "image/png")) }
+ let!(:user) { create(:user, :with_avatar) }
describe "GET show" do
context "when the namespace belongs to a user" do
diff --git a/spec/controllers/profiles/avatars_controller_spec.rb b/spec/controllers/profiles/avatars_controller_spec.rb
index ad5855df0a4..85dff009bcf 100644
--- a/spec/controllers/profiles/avatars_controller_spec.rb
+++ b/spec/controllers/profiles/avatars_controller_spec.rb
@@ -1,7 +1,7 @@
require 'spec_helper'
describe Profiles::AvatarsController do
- let(:user) { create(:user, avatar: fixture_file_upload(Rails.root + "spec/fixtures/dk.png")) }
+ let(:user) { create(:user, :with_avatar) }
before do
sign_in(user)
diff --git a/spec/controllers/profile_keys_controller_spec.rb b/spec/controllers/profiles/keys_controller_spec.rb
index b6573f105dc..b6573f105dc 100644
--- a/spec/controllers/profile_keys_controller_spec.rb
+++ b/spec/controllers/profiles/keys_controller_spec.rb
diff --git a/spec/controllers/blame_controller_spec.rb b/spec/controllers/projects/blame_controller_spec.rb
index 25f06299a29..25f06299a29 100644
--- a/spec/controllers/blame_controller_spec.rb
+++ b/spec/controllers/projects/blame_controller_spec.rb
diff --git a/spec/controllers/branches_controller_spec.rb b/spec/controllers/projects/branches_controller_spec.rb
index 8e06d4bdc77..8e06d4bdc77 100644
--- a/spec/controllers/branches_controller_spec.rb
+++ b/spec/controllers/projects/branches_controller_spec.rb
diff --git a/spec/controllers/projects/commit_controller_spec.rb b/spec/controllers/projects/commit_controller_spec.rb
new file mode 100644
index 00000000000..438e776ec4b
--- /dev/null
+++ b/spec/controllers/projects/commit_controller_spec.rb
@@ -0,0 +1,37 @@
+require 'rails_helper'
+
+describe Projects::CommitController do
+ describe 'GET show' do
+ let(:project) { create(:project) }
+
+ before do
+ user = create(:user)
+ project.team << [user, :master]
+
+ sign_in(user)
+ end
+
+ context 'with valid id' do
+ it 'responds with 200' do
+ go id: project.commit.id
+
+ expect(response).to be_ok
+ end
+ end
+
+ context 'with invalid id' do
+ it 'responds with 404' do
+ go id: project.commit.id.reverse
+
+ expect(response).to be_not_found
+ end
+ end
+
+ def go(id:)
+ get :show,
+ namespace_id: project.namespace.to_param,
+ project_id: project.to_param,
+ id: id
+ end
+ end
+end
diff --git a/spec/controllers/commits_controller_spec.rb b/spec/controllers/projects/commits_controller_spec.rb
index 7d8089c4bc6..7d8089c4bc6 100644
--- a/spec/controllers/commits_controller_spec.rb
+++ b/spec/controllers/projects/commits_controller_spec.rb
diff --git a/spec/controllers/projects/compare_controller_spec.rb b/spec/controllers/projects/compare_controller_spec.rb
index be19f1abc53..788a609ee40 100644
--- a/spec/controllers/projects/compare_controller_spec.rb
+++ b/spec/controllers/projects/compare_controller_spec.rb
@@ -19,7 +19,7 @@ describe Projects::CompareController do
to: ref_to)
expect(response).to be_success
- expect(assigns(:diffs).length).to be >= 1
+ expect(assigns(:diffs).first).to_not be_nil
expect(assigns(:commits).length).to be >= 1
end
@@ -32,10 +32,10 @@ describe Projects::CompareController do
w: 1)
expect(response).to be_success
- expect(assigns(:diffs).length).to be >= 1
+ expect(assigns(:diffs).first).to_not be_nil
expect(assigns(:commits).length).to be >= 1
# without whitespace option, there are more than 2 diff_splits
- diff_splits = assigns(:diffs)[0].diff.split("\n")
+ diff_splits = assigns(:diffs).first.diff.split("\n")
expect(diff_splits.length).to be <= 2
end
@@ -48,7 +48,7 @@ describe Projects::CompareController do
to: ref_to)
expect(response).to be_success
- expect(assigns(:diffs)).to eq([])
+ expect(assigns(:diffs).to_a).to eq([])
expect(assigns(:commits)).to eq([])
end
diff --git a/spec/controllers/projects/forks_controller_spec.rb b/spec/controllers/projects/forks_controller_spec.rb
new file mode 100644
index 00000000000..883bbaedd4e
--- /dev/null
+++ b/spec/controllers/projects/forks_controller_spec.rb
@@ -0,0 +1,72 @@
+require 'spec_helper'
+
+describe Projects::ForksController do
+ let(:user) { create(:user) }
+ let(:project) { create(:project, visibility_level: Project::PUBLIC) }
+ let(:forked_project) { Projects::ForkService.new(project, user).execute }
+ let(:group) { create(:group, owner: forked_project.creator) }
+
+ describe 'GET index' do
+ def get_forks
+ get :index,
+ namespace_id: project.namespace.to_param,
+ project_id: project.to_param
+ end
+
+ context 'when fork is public' do
+ before { forked_project.update_attribute(:visibility_level, Project::PUBLIC) }
+
+ it 'should be visible for non logged in users' do
+ get_forks
+
+ expect(assigns[:forks]).to be_present
+ end
+ end
+
+ context 'when fork is private' do
+ before do
+ forked_project.update_attributes(visibility_level: Project::PRIVATE, group: group)
+ end
+
+ it 'should not be visible for non logged in users' do
+ get_forks
+
+ expect(assigns[:forks]).to be_blank
+ end
+
+ context 'when user is logged in' do
+ before { sign_in(project.creator) }
+
+ context 'when user is not a Project member neither a group member' do
+ it 'should not see the Project listed' do
+ get_forks
+
+ expect(assigns[:forks]).to be_blank
+ end
+ end
+
+ context 'when user is a member of the Project' do
+ before { forked_project.team << [project.creator, :developer] }
+
+ it 'should see the project listed' do
+ get_forks
+
+ expect(assigns[:forks]).to be_present
+ end
+ end
+
+ context 'when user is a member of the Group' do
+ before { forked_project.group.add_developer(project.creator) }
+
+ it 'should see the project listed' do
+ get_forks
+
+ expect(assigns[:forks]).to be_present
+ end
+ end
+
+ end
+ end
+ end
+
+end
diff --git a/spec/controllers/projects/imports_controller_spec.rb b/spec/controllers/projects/imports_controller_spec.rb
index 85d1d1e0524..0147bd2b953 100644
--- a/spec/controllers/projects/imports_controller_spec.rb
+++ b/spec/controllers/projects/imports_controller_spec.rb
@@ -104,6 +104,18 @@ describe Projects::ImportsController do
end
end
end
+
+ context 'when import never happened' do
+ before do
+ project.update_attribute(:import_status, :none)
+ end
+
+ it 'redirects to namespace_project_path' do
+ get :show, namespace_id: project.namespace.to_param, project_id: project.to_param
+
+ expect(response).to redirect_to namespace_project_path(project.namespace, project)
+ end
+ end
end
end
end
diff --git a/spec/controllers/projects/merge_requests_controller_spec.rb b/spec/controllers/projects/merge_requests_controller_spec.rb
index 9450a389d81..e82fe26c7a6 100644
--- a/spec/controllers/projects/merge_requests_controller_spec.rb
+++ b/spec/controllers/projects/merge_requests_controller_spec.rb
@@ -123,6 +123,40 @@ describe Projects::MergeRequestsController do
end
end
+ describe 'GET #index' do
+ def get_merge_requests
+ get :index,
+ namespace_id: project.namespace.to_param,
+ project_id: project.to_param,
+ state: 'opened'
+ end
+
+ context 'when filtering by opened state' do
+
+ context 'with opened merge requests' do
+ it 'should list those merge requests' do
+ get_merge_requests
+
+ expect(assigns(:merge_requests)).to include(merge_request)
+ end
+ end
+
+ context 'with reopened merge requests' do
+ before do
+ merge_request.close!
+ merge_request.reopen!
+ end
+
+ it 'should list those merge requests' do
+ get_merge_requests
+
+ expect(assigns(:merge_requests)).to include(merge_request)
+ end
+ end
+
+ end
+ end
+
describe 'GET diffs' do
def go(format: 'html')
get :diffs,
diff --git a/spec/controllers/projects/repositories_controller_spec.rb b/spec/controllers/projects/repositories_controller_spec.rb
index 18a30033ed8..09ec4f18f9d 100644
--- a/spec/controllers/projects/repositories_controller_spec.rb
+++ b/spec/controllers/projects/repositories_controller_spec.rb
@@ -8,15 +8,10 @@ describe Projects::RepositoriesController do
before do
sign_in(user)
project.team << [user, :developer]
-
- allow(ArchiveRepositoryService).to receive(:new).and_return(service)
end
- let(:service) { ArchiveRepositoryService.new(project, "master", "zip") }
-
- it "executes ArchiveRepositoryService" do
- expect(ArchiveRepositoryService).to receive(:new).with(project, "master", "zip")
- expect(service).to receive(:execute)
+ it "uses Gitlab::Workhorse" do
+ expect(Gitlab::Workhorse).to receive(:send_git_archive).with(project, "master", "zip")
get :archive, namespace_id: project.namespace.path, project_id: project.path, ref: "master", format: "zip"
end
@@ -24,7 +19,7 @@ describe Projects::RepositoriesController do
context "when the service raises an error" do
before do
- allow(service).to receive(:execute).and_raise("Archive failed")
+ allow(Gitlab::Workhorse).to receive(:send_git_archive).and_raise("Archive failed")
end
it "renders Not Found" do
diff --git a/spec/controllers/sent_notification_controller_spec.rb b/spec/controllers/sent_notifications_controller_spec.rb
index 9ced397bd4a..9ced397bd4a 100644
--- a/spec/controllers/sent_notification_controller_spec.rb
+++ b/spec/controllers/sent_notifications_controller_spec.rb
diff --git a/spec/controllers/uploads_controller_spec.rb b/spec/controllers/uploads_controller_spec.rb
index af5d043cf02..0d9f4b299bc 100644
--- a/spec/controllers/uploads_controller_spec.rb
+++ b/spec/controllers/uploads_controller_spec.rb
@@ -1,7 +1,7 @@
require 'spec_helper'
describe UploadsController do
- let!(:user) { create(:user, avatar: fixture_file_upload(Rails.root + "spec/fixtures/dk.png", "image/png")) }
+ let!(:user) { create(:user, :with_avatar) }
describe "GET show" do
context "when viewing a user avatar" do
diff --git a/spec/controllers/users_controller_spec.rb b/spec/controllers/users_controller_spec.rb
index 104a5f50143..7337ff58be1 100644
--- a/spec/controllers/users_controller_spec.rb
+++ b/spec/controllers/users_controller_spec.rb
@@ -41,6 +41,7 @@ describe UsersController do
end
describe 'GET #calendar' do
+
it 'renders calendar' do
sign_in(user)
@@ -48,6 +49,23 @@ describe UsersController do
expect(response).to render_template('calendar')
end
+
+ context 'forked project' do
+ let!(:project) { create(:project) }
+ let!(:forked_project) { Projects::ForkService.new(project, user).execute }
+
+ before do
+ sign_in(user)
+ project.team << [user, :developer]
+ EventCreateService.new.push(project, user, [])
+ EventCreateService.new.push(forked_project, user, [])
+ end
+
+ it 'includes forked projects' do
+ get :calendar, username: user.username
+ expect(assigns(:contributions_calendar).projects.count).to eq(2)
+ end
+ end
end
describe 'GET #calendar_activities' do
diff --git a/spec/factories.rb b/spec/factories.rb
deleted file mode 100644
index 2a81684dfcf..00000000000
--- a/spec/factories.rb
+++ /dev/null
@@ -1,222 +0,0 @@
-include ActionDispatch::TestProcess
-
-FactoryGirl.define do
- sequence :sentence, aliases: [:title, :content] do
- FFaker::Lorem.sentence
- end
-
- sequence :name do
- FFaker::Name.name
- end
-
- sequence :file_name do
- FFaker::Internet.user_name
- end
-
- sequence(:url) { FFaker::Internet.uri('http') }
-
- factory :user, aliases: [:author, :assignee, :owner, :creator] do
- email { FFaker::Internet.email }
- name
- sequence(:username) { |n| "#{FFaker::Internet.user_name}#{n}" }
- password "12345678"
- confirmed_at { Time.now }
- confirmation_token { nil }
- can_create_group true
-
- trait :admin do
- admin true
- end
-
- trait :two_factor do
- before(:create) do |user|
- user.two_factor_enabled = true
- user.otp_secret = User.generate_otp_secret(32)
- user.generate_otp_backup_codes!
- end
- end
-
- factory :omniauth_user do
- ignore do
- extern_uid '123456'
- provider 'ldapmain'
- end
-
- after(:create) do |user, evaluator|
- user.identities << create(
- :identity,
- provider: evaluator.provider,
- extern_uid: evaluator.extern_uid
- )
- end
- end
-
- factory :admin, traits: [:admin]
- end
-
- factory :group do
- sequence(:name) { |n| "group#{n}" }
- path { name.downcase.gsub(/\s/, '_') }
- type 'Group'
- end
-
- factory :namespace do
- sequence(:name) { |n| "namespace#{n}" }
- path { name.downcase.gsub(/\s/, '_') }
- owner
- end
-
- factory :project_member do
- user
- project
- access_level { ProjectMember::MASTER }
- end
-
- factory :issue do
- title
- author
- project
-
- trait :closed do
- state :closed
- end
-
- trait :reopened do
- state :reopened
- end
-
- factory :closed_issue, traits: [:closed]
- factory :reopened_issue, traits: [:reopened]
- end
-
- factory :event do
- factory :closed_issue_event do
- project
- action { Event::CLOSED }
- target factory: :closed_issue
- author factory: :user
- end
- end
-
- factory :key do
- title
- key do
- "ssh-rsa AAAAB3NzaC1yc2EAAAABJQAAAIEAiPWx6WM4lhHNedGfBpPJNPpZ7yKu+dnn1SJejgt4596k6YjzGGphH2TUxwKzxcKDKKezwkpfnxPkSMkuEspGRt/aZZ9wa++Oi7Qkr8prgHc4soW6NUlfDzpvZK2H5E7eQaSeP3SAwGmQKUFHCddNaP0L+hM7zhFNzjFvpaMgJw0= dummy@gitlab.com"
- end
-
- factory :deploy_key, class: 'DeployKey' do
- end
-
- factory :personal_key do
- user
- end
-
- factory :another_key do
- key do
- "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDmTillFzNTrrGgwaCKaSj+QCz81E6jBc/s9av0+3b1Hwfxgkqjl4nAK/OD2NjgyrONDTDfR8cRN4eAAy6nY8GLkOyYBDyuc5nTMqs5z3yVuTwf3koGm/YQQCmo91psZ2BgDFTor8SVEE5Mm1D1k3JDMhDFxzzrOtRYFPci9lskTJaBjpqWZ4E9rDTD2q/QZntCqbC3wE9uSemRQB5f8kik7vD/AD8VQXuzKladrZKkzkONCPWsXDspUitjM8HkQdOf0PsYn1CMUC1xKYbCxkg5TkEosIwGv6CoEArUrdu/4+10LVslq494mAvEItywzrluCLCnwELfW+h/m8UHoVhZ"
- end
-
- factory :another_deploy_key, class: 'DeployKey' do
- end
- end
- end
-
- factory :email do
- user
- email do
- FFaker::Internet.email('alias')
- end
-
- factory :another_email do
- email do
- FFaker::Internet.email('another.alias')
- end
- end
- end
-
- factory :milestone do
- title
- project
-
- trait :closed do
- state :closed
- end
-
- factory :closed_milestone, traits: [:closed]
- end
-
- factory :system_hook do
- url
- end
-
- factory :project_hook do
- url
- end
-
- factory :project_snippet do
- project
- author
- title
- content
- file_name
- end
-
- factory :personal_snippet do
- author
- title
- content
- file_name
-
- trait :public do
- visibility_level Gitlab::VisibilityLevel::PUBLIC
- end
-
- trait :internal do
- visibility_level Gitlab::VisibilityLevel::INTERNAL
- end
-
- trait :private do
- visibility_level Gitlab::VisibilityLevel::PRIVATE
- end
- end
-
- factory :snippet do
- author
- title
- content
- file_name
- end
-
- factory :protected_branch do
- name
- project
- end
-
- factory :service do
- type ""
- title "GitLab CI"
- project
- end
-
- factory :service_hook do
- url
- service
- end
-
- factory :deploy_keys_project do
- deploy_key
- project
- end
-
- factory :identity do
- provider 'ldapmain'
- extern_uid 'my-ldap-id'
- end
-
- factory :sent_notification do
- project
- recipient factory: :user
- noteable factory: :issue
- reply_key "0123456789abcdef" * 2
- end
-end
diff --git a/spec/factories/abuse_reports.rb b/spec/factories/abuse_reports.rb
index 8d287ded292..d0e8c778518 100644
--- a/spec/factories/abuse_reports.rb
+++ b/spec/factories/abuse_reports.rb
@@ -10,8 +10,6 @@
# updated_at :datetime
#
-# Read about factories at https://github.com/thoughtbot/factory_girl
-
FactoryGirl.define do
factory :abuse_report do
reporter factory: :user
diff --git a/spec/factories/appearances.rb b/spec/factories/appearances.rb
new file mode 100644
index 00000000000..cf2a2b76bcb
--- /dev/null
+++ b/spec/factories/appearances.rb
@@ -0,0 +1,8 @@
+# Read about factories at https://github.com/thoughtbot/factory_girl
+
+FactoryGirl.define do
+ factory :appearance do
+ title "MepMep"
+ description "This is my Community Edition instance"
+ end
+end
diff --git a/spec/factories/broadcast_messages.rb b/spec/factories/broadcast_messages.rb
index 978a7d4cecb..373ca75467e 100644
--- a/spec/factories/broadcast_messages.rb
+++ b/spec/factories/broadcast_messages.rb
@@ -12,8 +12,6 @@
# font :string(255)
#
-# Read about factories at https://github.com/thoughtbot/factory_girl
-
FactoryGirl.define do
factory :broadcast_message do
message "MyText"
diff --git a/spec/factories/ci/builds.rb b/spec/factories/ci/builds.rb
index c1b6ecd329a..cd49e559b7d 100644
--- a/spec/factories/ci/builds.rb
+++ b/spec/factories/ci/builds.rb
@@ -1,3 +1,5 @@
+include ActionDispatch::TestProcess
+
FactoryGirl.define do
factory :ci_build, class: Ci::Build do
name 'test'
@@ -16,10 +18,30 @@ FactoryGirl.define do
commit factory: :ci_commit
+ trait :success do
+ status 'success'
+ end
+
+ trait :failed do
+ status 'failed'
+ end
+
trait :canceled do
status 'canceled'
end
+ trait :running do
+ status 'running'
+ end
+
+ trait :pending do
+ status 'pending'
+ end
+
+ trait :allowed_to_fail do
+ allow_failure true
+ end
+
after(:build) do |build, evaluator|
build.project = build.commit.project
end
@@ -33,8 +55,12 @@ FactoryGirl.define do
tag true
end
- factory :ci_build_with_trace do
- after(:create) do |build, evaluator|
+ factory :ci_build_with_coverage do
+ coverage 99.9
+ end
+
+ trait :trace do
+ after(:create) do |build, evaluator|
build.trace = 'BUILD TRACE'
end
end
@@ -42,14 +68,13 @@ FactoryGirl.define do
trait :artifacts do
after(:create) do |build, _|
build.artifacts_file =
- fixture_file_upload(Rails.root +
- 'spec/fixtures/ci_build_artifacts.zip',
- 'application/zip')
+ fixture_file_upload(Rails.root.join('spec/fixtures/ci_build_artifacts.zip'),
+ 'application/zip')
build.artifacts_metadata =
- fixture_file_upload(Rails.root +
- 'spec/fixtures/ci_build_artifacts_metadata.gz',
- 'application/x-gzip')
+ fixture_file_upload(Rails.root.join('spec/fixtures/ci_build_artifacts_metadata.gz'),
+ 'application/x-gzip')
+
build.save!
end
end
diff --git a/spec/factories/ci/commits.rb b/spec/factories/ci/commits.rb
index b42cafa518a..645cd7ae766 100644
--- a/spec/factories/ci/commits.rb
+++ b/spec/factories/ci/commits.rb
@@ -16,7 +16,6 @@
# gl_project_id :integer
#
-# Read about factories at https://github.com/thoughtbot/factory_girl
FactoryGirl.define do
factory :ci_empty_commit, class: Ci::Commit do
sha '97de212e80737a608d939f648d959671fb0a0142'
diff --git a/spec/factories/ci/runner_projects.rb b/spec/factories/ci/runner_projects.rb
index 008d1c5d961..83fccad679f 100644
--- a/spec/factories/ci/runner_projects.rb
+++ b/spec/factories/ci/runner_projects.rb
@@ -9,8 +9,6 @@
# updated_at :datetime
#
-# Read about factories at https://github.com/thoughtbot/factory_girl
-
FactoryGirl.define do
factory :ci_runner_project, class: Ci::RunnerProject do
runner_id 1
diff --git a/spec/factories/ci/runners.rb b/spec/factories/ci/runners.rb
index db759eca9ac..5b645fab32e 100644
--- a/spec/factories/ci/runners.rb
+++ b/spec/factories/ci/runners.rb
@@ -17,22 +17,18 @@
# architecture :string(255)
#
-# Read about factories at https://github.com/thoughtbot/factory_girl
-
FactoryGirl.define do
factory :ci_runner, class: Ci::Runner do
sequence :description do |n|
"My runner#{n}"
end
- platform "darwin"
+ platform "darwin"
+ is_shared false
+ active true
- factory :ci_shared_runner do
+ trait :shared do
is_shared true
end
-
- factory :ci_specific_runner do
- is_shared false
- end
end
end
diff --git a/spec/factories/ci/trigger_requests.rb b/spec/factories/ci/trigger_requests.rb
index 2c0d004d267..6d47d05f8ad 100644
--- a/spec/factories/ci/trigger_requests.rb
+++ b/spec/factories/ci/trigger_requests.rb
@@ -1,5 +1,3 @@
-# Read about factories at https://github.com/thoughtbot/factory_girl
-
FactoryGirl.define do
factory :ci_trigger_request, class: Ci::TriggerRequest do
factory :ci_trigger_request_with_variables do
diff --git a/spec/factories/ci/triggers.rb b/spec/factories/ci/triggers.rb
index fd3afdb1ec2..a27b04424e5 100644
--- a/spec/factories/ci/triggers.rb
+++ b/spec/factories/ci/triggers.rb
@@ -1,5 +1,3 @@
-# Read about factories at https://github.com/thoughtbot/factory_girl
-
FactoryGirl.define do
factory :ci_trigger_without_token, class: Ci::Trigger do
factory :ci_trigger do
diff --git a/spec/factories/ci/variables.rb b/spec/factories/ci/variables.rb
index 8f62d64411b..856a8e725eb 100644
--- a/spec/factories/ci/variables.rb
+++ b/spec/factories/ci/variables.rb
@@ -12,8 +12,6 @@
# gl_project_id :integer
#
-# Read about factories at https://github.com/thoughtbot/factory_girl
-
FactoryGirl.define do
factory :ci_variable, class: Ci::Variable do
sequence(:key) { |n| "VARIABLE_#{n}" }
diff --git a/spec/factories/deploy_keys_projects.rb b/spec/factories/deploy_keys_projects.rb
new file mode 100644
index 00000000000..27cece487bd
--- /dev/null
+++ b/spec/factories/deploy_keys_projects.rb
@@ -0,0 +1,6 @@
+FactoryGirl.define do
+ factory :deploy_keys_project do
+ deploy_key
+ project
+ end
+end
diff --git a/spec/factories/emails.rb b/spec/factories/emails.rb
new file mode 100644
index 00000000000..9794772ac7d
--- /dev/null
+++ b/spec/factories/emails.rb
@@ -0,0 +1,6 @@
+FactoryGirl.define do
+ factory :email do
+ user
+ email { FFaker::Internet.email('alias') }
+ end
+end
diff --git a/spec/factories/events.rb b/spec/factories/events.rb
new file mode 100644
index 00000000000..90788f30ac9
--- /dev/null
+++ b/spec/factories/events.rb
@@ -0,0 +1,10 @@
+FactoryGirl.define do
+ factory :event do
+ factory :closed_issue_event do
+ project
+ action { Event::CLOSED }
+ target factory: :closed_issue
+ author factory: :user
+ end
+ end
+end
diff --git a/spec/factories/forked_project_links.rb b/spec/factories/forked_project_links.rb
index 906e4106b32..252bf2747e1 100644
--- a/spec/factories/forked_project_links.rb
+++ b/spec/factories/forked_project_links.rb
@@ -9,8 +9,6 @@
# updated_at :datetime
#
-# Read about factories at https://github.com/thoughtbot/factory_girl
-
FactoryGirl.define do
factory :forked_project_link do
association :forked_to_project, factory: :project
diff --git a/spec/factories/groups.rb b/spec/factories/groups.rb
new file mode 100644
index 00000000000..4a3a155d7ff
--- /dev/null
+++ b/spec/factories/groups.rb
@@ -0,0 +1,7 @@
+FactoryGirl.define do
+ factory :group do
+ sequence(:name) { |n| "group#{n}" }
+ path { name.downcase.gsub(/\s/, '_') }
+ type 'Group'
+ end
+end
diff --git a/spec/factories/identities.rb b/spec/factories/identities.rb
new file mode 100644
index 00000000000..26ef6f18698
--- /dev/null
+++ b/spec/factories/identities.rb
@@ -0,0 +1,6 @@
+FactoryGirl.define do
+ factory :identity do
+ provider 'ldapmain'
+ extern_uid 'my-ldap-id'
+ end
+end
diff --git a/spec/factories/issues.rb b/spec/factories/issues.rb
new file mode 100644
index 00000000000..722095de590
--- /dev/null
+++ b/spec/factories/issues.rb
@@ -0,0 +1,18 @@
+FactoryGirl.define do
+ factory :issue do
+ title
+ author
+ project
+
+ trait :closed do
+ state :closed
+ end
+
+ trait :reopened do
+ state :reopened
+ end
+
+ factory :closed_issue, traits: [:closed]
+ factory :reopened_issue, traits: [:reopened]
+ end
+end
diff --git a/spec/factories/keys.rb b/spec/factories/keys.rb
new file mode 100644
index 00000000000..d69c5b38d0a
--- /dev/null
+++ b/spec/factories/keys.rb
@@ -0,0 +1,24 @@
+FactoryGirl.define do
+ factory :key do
+ title
+ key do
+ "ssh-rsa AAAAB3NzaC1yc2EAAAABJQAAAIEAiPWx6WM4lhHNedGfBpPJNPpZ7yKu+dnn1SJejgt4596k6YjzGGphH2TUxwKzxcKDKKezwkpfnxPkSMkuEspGRt/aZZ9wa++Oi7Qkr8prgHc4soW6NUlfDzpvZK2H5E7eQaSeP3SAwGmQKUFHCddNaP0L+hM7zhFNzjFvpaMgJw0= dummy@gitlab.com"
+ end
+
+ factory :deploy_key, class: 'DeployKey' do
+ end
+
+ factory :personal_key do
+ user
+ end
+
+ factory :another_key do
+ key do
+ "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDmTillFzNTrrGgwaCKaSj+QCz81E6jBc/s9av0+3b1Hwfxgkqjl4nAK/OD2NjgyrONDTDfR8cRN4eAAy6nY8GLkOyYBDyuc5nTMqs5z3yVuTwf3koGm/YQQCmo91psZ2BgDFTor8SVEE5Mm1D1k3JDMhDFxzzrOtRYFPci9lskTJaBjpqWZ4E9rDTD2q/QZntCqbC3wE9uSemRQB5f8kik7vD/AD8VQXuzKladrZKkzkONCPWsXDspUitjM8HkQdOf0PsYn1CMUC1xKYbCxkg5TkEosIwGv6CoEArUrdu/4+10LVslq494mAvEItywzrluCLCnwELfW+h/m8UHoVhZ"
+ end
+
+ factory :another_deploy_key, class: 'DeployKey' do
+ end
+ end
+ end
+end
diff --git a/spec/factories/label_links.rb b/spec/factories/label_links.rb
index bd304b5db6b..2939d4307c5 100644
--- a/spec/factories/label_links.rb
+++ b/spec/factories/label_links.rb
@@ -10,8 +10,6 @@
# updated_at :datetime
#
-# Read about factories at https://github.com/thoughtbot/factory_girl
-
FactoryGirl.define do
factory :label_link do
label
diff --git a/spec/factories/labels.rb b/spec/factories/labels.rb
index 8b12ee11af5..6e70af10af3 100644
--- a/spec/factories/labels.rb
+++ b/spec/factories/labels.rb
@@ -11,8 +11,6 @@
# template :boolean default(FALSE)
#
-# Read about factories at https://github.com/thoughtbot/factory_girl
-
FactoryGirl.define do
factory :label do
title "Bug"
diff --git a/spec/factories/lfs_objects.rb b/spec/factories/lfs_objects.rb
index 2da107ba24b..327858ce435 100644
--- a/spec/factories/lfs_objects.rb
+++ b/spec/factories/lfs_objects.rb
@@ -10,7 +10,7 @@
# file :string(255)
#
-# Read about factories at https://github.com/thoughtbot/factory_girl
+include ActionDispatch::TestProcess
FactoryGirl.define do
factory :lfs_object do
diff --git a/spec/factories/lfs_objects_projects.rb b/spec/factories/lfs_objects_projects.rb
index 3772236a77a..50b45843c99 100644
--- a/spec/factories/lfs_objects_projects.rb
+++ b/spec/factories/lfs_objects_projects.rb
@@ -9,8 +9,6 @@
# updated_at :datetime
#
-# Read about factories at https://github.com/thoughtbot/factory_girl
-
FactoryGirl.define do
factory :lfs_objects_project do
lfs_object
diff --git a/spec/factories/merge_requests.rb b/spec/factories/merge_requests.rb
index 0c6a881f868..ca1c636fce4 100644
--- a/spec/factories/merge_requests.rb
+++ b/spec/factories/merge_requests.rb
@@ -24,6 +24,7 @@
# merge_params :text
# merge_when_build_succeeds :boolean default(FALSE), not null
# merge_user_id :integer
+# merge_commit_sha :string
#
FactoryGirl.define do
@@ -68,6 +69,16 @@ FactoryGirl.define do
target_branch "master"
end
+ trait :rebased do
+ source_branch "markdown"
+ target_branch "improve/awesome"
+ end
+
+ trait :diverged do
+ source_branch "feature"
+ target_branch "master"
+ end
+
trait :merge_when_build_succeeds do
merge_when_build_succeeds true
merge_user author
diff --git a/spec/factories/milestones.rb b/spec/factories/milestones.rb
new file mode 100644
index 00000000000..e9e85962fe4
--- /dev/null
+++ b/spec/factories/milestones.rb
@@ -0,0 +1,12 @@
+FactoryGirl.define do
+ factory :milestone do
+ title
+ project
+
+ trait :closed do
+ state :closed
+ end
+
+ factory :closed_milestone, traits: [:closed]
+ end
+end
diff --git a/spec/factories/namespaces.rb b/spec/factories/namespaces.rb
new file mode 100644
index 00000000000..1b1fc4ce80d
--- /dev/null
+++ b/spec/factories/namespaces.rb
@@ -0,0 +1,7 @@
+FactoryGirl.define do
+ factory :namespace do
+ sequence(:name) { |n| "namespace#{n}" }
+ path { name.downcase.gsub(/\s/, '_') }
+ owner
+ end
+end
diff --git a/spec/factories/notes.rb b/spec/factories/notes.rb
index 35a20adeef3..e5dcb159014 100644
--- a/spec/factories/notes.rb
+++ b/spec/factories/notes.rb
@@ -21,6 +21,8 @@
require_relative '../support/repo_helpers'
+include ActionDispatch::TestProcess
+
FactoryGirl.define do
factory :note do
project
@@ -34,6 +36,8 @@ FactoryGirl.define do
factory :note_on_merge_request_diff, traits: [:on_merge_request, :on_diff]
factory :note_on_project_snippet, traits: [:on_project_snippet]
factory :system_note, traits: [:system]
+ factory :downvote_note, traits: [:award, :downvote]
+ factory :upvote_note, traits: [:award, :upvote]
trait :on_commit do
project
@@ -65,6 +69,18 @@ FactoryGirl.define do
system true
end
+ trait :award do
+ is_award true
+ end
+
+ trait :downvote do
+ note "thumbsdown"
+ end
+
+ trait :upvote do
+ note "thumbsup"
+ end
+
trait :with_attachment do
attachment { fixture_file_upload(Rails.root + "spec/fixtures/dk.png", "`/png") }
end
diff --git a/spec/factories/personal_snippets.rb b/spec/factories/personal_snippets.rb
new file mode 100644
index 00000000000..b493a6968ff
--- /dev/null
+++ b/spec/factories/personal_snippets.rb
@@ -0,0 +1,15 @@
+FactoryGirl.define do
+ factory :personal_snippet, parent: :snippet, class: :PersonalSnippet do
+ trait :public do
+ visibility_level PersonalSnippet::PUBLIC
+ end
+
+ trait :internal do
+ visibility_level PersonalSnippet::INTERNAL
+ end
+
+ trait :private do
+ visibility_level PersonalSnippet::PRIVATE
+ end
+ end
+end
diff --git a/spec/factories/project_hooks.rb b/spec/factories/project_hooks.rb
new file mode 100644
index 00000000000..94dd935a039
--- /dev/null
+++ b/spec/factories/project_hooks.rb
@@ -0,0 +1,5 @@
+FactoryGirl.define do
+ factory :project_hook do
+ url { FFaker::Internet.uri('http') }
+ end
+end
diff --git a/spec/factories/project_members.rb b/spec/factories/project_members.rb
new file mode 100644
index 00000000000..cf3659ba275
--- /dev/null
+++ b/spec/factories/project_members.rb
@@ -0,0 +1,27 @@
+FactoryGirl.define do
+ factory :project_member do
+ user
+ project
+ master
+
+ trait :guest do
+ access_level ProjectMember::GUEST
+ end
+
+ trait :reporter do
+ access_level ProjectMember::REPORTER
+ end
+
+ trait :developer do
+ access_level ProjectMember::DEVELOPER
+ end
+
+ trait :master do
+ access_level ProjectMember::MASTER
+ end
+
+ trait :owner do
+ access_level ProjectMember::OWNER
+ end
+ end
+end
diff --git a/spec/factories/project_snippets.rb b/spec/factories/project_snippets.rb
new file mode 100644
index 00000000000..154442bd3db
--- /dev/null
+++ b/spec/factories/project_snippets.rb
@@ -0,0 +1,9 @@
+FactoryGirl.define do
+ factory :project_snippet do
+ project
+ author
+ title
+ content
+ file_name
+ end
+end
diff --git a/spec/factories/protected_branches.rb b/spec/factories/protected_branches.rb
new file mode 100644
index 00000000000..28ed8078157
--- /dev/null
+++ b/spec/factories/protected_branches.rb
@@ -0,0 +1,6 @@
+FactoryGirl.define do
+ factory :protected_branch do
+ name
+ project
+ end
+end
diff --git a/spec/factories/releases.rb b/spec/factories/releases.rb
index 43d09b17534..7f331c37256 100644
--- a/spec/factories/releases.rb
+++ b/spec/factories/releases.rb
@@ -10,8 +10,6 @@
# updated_at :datetime
#
-# Read about factories at https://github.com/thoughtbot/factory_girl
-
FactoryGirl.define do
factory :release do
tag "v1.1.0"
diff --git a/spec/factories/sent_notifications.rb b/spec/factories/sent_notifications.rb
new file mode 100644
index 00000000000..78eb929c6e7
--- /dev/null
+++ b/spec/factories/sent_notifications.rb
@@ -0,0 +1,8 @@
+FactoryGirl.define do
+ factory :sent_notification do
+ project
+ recipient factory: :user
+ noteable factory: :issue
+ reply_key "0123456789abcdef" * 2
+ end
+end
diff --git a/spec/factories/service_hooks.rb b/spec/factories/service_hooks.rb
new file mode 100644
index 00000000000..6dd6af63f3e
--- /dev/null
+++ b/spec/factories/service_hooks.rb
@@ -0,0 +1,6 @@
+FactoryGirl.define do
+ factory :service_hook do
+ url { FFaker::Internet.uri('http') }
+ service
+ end
+end
diff --git a/spec/factories/services.rb b/spec/factories/services.rb
new file mode 100644
index 00000000000..9de78d68280
--- /dev/null
+++ b/spec/factories/services.rb
@@ -0,0 +1,5 @@
+FactoryGirl.define do
+ factory :service do
+ project
+ end
+end
diff --git a/spec/factories/snippets.rb b/spec/factories/snippets.rb
new file mode 100644
index 00000000000..b9127b3d75e
--- /dev/null
+++ b/spec/factories/snippets.rb
@@ -0,0 +1,16 @@
+FactoryGirl.define do
+ sequence :title, aliases: [:content] do
+ FFaker::Lorem.sentence
+ end
+
+ sequence :file_name do
+ FFaker::Internet.user_name
+ end
+
+ factory :snippet do
+ author
+ title
+ content
+ file_name
+ end
+end
diff --git a/spec/factories/spam_logs.rb b/spec/factories/spam_logs.rb
index d90e5d6bf26..a4f6d291269 100644
--- a/spec/factories/spam_logs.rb
+++ b/spec/factories/spam_logs.rb
@@ -1,5 +1,3 @@
-# Read about factories at https://github.com/thoughtbot/factory_girl
-
FactoryGirl.define do
factory :spam_log do
user
diff --git a/spec/factories/system_hooks.rb b/spec/factories/system_hooks.rb
new file mode 100644
index 00000000000..c786e9cb79b
--- /dev/null
+++ b/spec/factories/system_hooks.rb
@@ -0,0 +1,5 @@
+FactoryGirl.define do
+ factory :system_hook do
+ url { FFaker::Internet.uri('http') }
+ end
+end
diff --git a/spec/factories/todos.rb b/spec/factories/todos.rb
new file mode 100644
index 00000000000..bd85b1d798a
--- /dev/null
+++ b/spec/factories/todos.rb
@@ -0,0 +1,34 @@
+# == Schema Information
+#
+# Table name: todos
+#
+# id :integer not null, primary key
+# user_id :integer not null
+# project_id :integer not null
+# target_id :integer not null
+# target_type :string not null
+# author_id :integer
+# note_id :integer
+# action :integer not null
+# state :string not null
+# created_at :datetime
+# updated_at :datetime
+#
+
+FactoryGirl.define do
+ factory :todo do
+ project
+ author
+ user
+ target factory: :issue
+ action { Todo::ASSIGNED }
+
+ trait :assigned do
+ action { Todo::ASSIGNED }
+ end
+
+ trait :mentioned do
+ action { Todo::MENTIONED }
+ end
+ end
+end
diff --git a/spec/factories/users.rb b/spec/factories/users.rb
new file mode 100644
index 00000000000..785c2a3d811
--- /dev/null
+++ b/spec/factories/users.rb
@@ -0,0 +1,50 @@
+FactoryGirl.define do
+ sequence(:name) { FFaker::Name.name }
+
+ factory :user, aliases: [:author, :assignee, :recipient, :owner, :creator] do
+ email { FFaker::Internet.email }
+ name
+ sequence(:username) { |n| "#{FFaker::Internet.user_name}#{n}" }
+ password "12345678"
+ confirmed_at { Time.now }
+ confirmation_token { nil }
+ can_create_group true
+
+ trait :admin do
+ admin true
+ end
+
+ trait :two_factor do
+ before(:create) do |user|
+ user.two_factor_enabled = true
+ user.otp_secret = User.generate_otp_secret(32)
+ user.otp_grace_period_started_at = Time.now
+ user.generate_otp_backup_codes!
+ end
+ end
+
+ trait :with_avatar do
+ avatar { fixture_file_upload(Rails.root.join(*%w(spec fixtures dk.png)), 'image/png') }
+ avatar_crop_x 0
+ avatar_crop_y 0
+ avatar_crop_size 256
+ end
+
+ factory :omniauth_user do
+ transient do
+ extern_uid '123456'
+ provider 'ldapmain'
+ end
+
+ after(:create) do |user, evaluator|
+ user.identities << create(
+ :identity,
+ provider: evaluator.provider,
+ extern_uid: evaluator.extern_uid
+ )
+ end
+ end
+
+ factory :admin, traits: [:admin]
+ end
+end
diff --git a/spec/features/issues/filter_by_milestone_spec.rb b/spec/features/issues/filter_by_milestone_spec.rb
index 38c8d343ce3..591866b40d4 100644
--- a/spec/features/issues/filter_by_milestone_spec.rb
+++ b/spec/features/issues/filter_by_milestone_spec.rb
@@ -13,7 +13,7 @@ feature 'Issue filtering by Milestone', feature: true do
visit_issues(project)
filter_by_milestone(Milestone::None.title)
- expect(page).to have_css('.title', count: 1)
+ expect(page).to have_css('.issue .title', count: 1)
end
scenario 'filters by a specific Milestone', js: true do
@@ -23,7 +23,7 @@ feature 'Issue filtering by Milestone', feature: true do
visit_issues(project)
filter_by_milestone(milestone.title)
- expect(page).to have_css('.title', count: 1)
+ expect(page).to have_css('.issue .title', count: 1)
end
def visit_issues(project)
diff --git a/spec/features/login_spec.rb b/spec/features/login_spec.rb
index dac9205449a..4433ef2d6f1 100644
--- a/spec/features/login_spec.rb
+++ b/spec/features/login_spec.rb
@@ -1,6 +1,32 @@
require 'spec_helper'
feature 'Login', feature: true do
+ describe 'initial login after setup' do
+ it 'allows the initial admin to create a password' do
+ # This behavior is dependent on there only being one user
+ User.delete_all
+
+ user = create(:admin, password_automatically_set: true)
+
+ visit root_path
+ expect(current_path).to eq edit_user_password_path
+ expect(page).to have_content('Please create a password for your new account.')
+
+ fill_in 'user_password', with: 'password'
+ fill_in 'user_password_confirmation', with: 'password'
+ click_button 'Change your password'
+
+ expect(current_path).to eq new_user_session_path
+ expect(page).to have_content(I18n.t('devise.passwords.updated_not_active'))
+
+ fill_in 'user_login', with: user.username
+ fill_in 'user_password', with: 'password'
+ click_button 'Sign in'
+
+ expect(current_path).to eq root_path
+ end
+ end
+
describe 'with two-factor authentication' do
context 'with valid username/password' do
let(:user) { create(:user, :two_factor) }
diff --git a/spec/features/runners_spec.rb b/spec/features/runners_spec.rb
index d97831aae14..e8886e7edf9 100644
--- a/spec/features/runners_spec.rb
+++ b/spec/features/runners_spec.rb
@@ -17,10 +17,10 @@ describe "Runners" do
@project3 = FactoryGirl.create :empty_project
@project3.team << [user, :developer]
- @shared_runner = FactoryGirl.create :ci_shared_runner
- @specific_runner = FactoryGirl.create :ci_specific_runner
- @specific_runner2 = FactoryGirl.create :ci_specific_runner
- @specific_runner3 = FactoryGirl.create :ci_specific_runner
+ @shared_runner = FactoryGirl.create :ci_runner, :shared
+ @specific_runner = FactoryGirl.create :ci_runner
+ @specific_runner2 = FactoryGirl.create :ci_runner
+ @specific_runner3 = FactoryGirl.create :ci_runner
@project.runners << @specific_runner
@project2.runners << @specific_runner2
@project3.runners << @specific_runner3
@@ -84,7 +84,7 @@ describe "Runners" do
before do
@project = FactoryGirl.create :empty_project
@project.team << [user, :master]
- @specific_runner = FactoryGirl.create :ci_specific_runner
+ @specific_runner = FactoryGirl.create :ci_runner
@project.runners << @specific_runner
end
diff --git a/spec/fixtures/mail_room_disabled.yml b/spec/fixtures/mail_room_disabled.yml
new file mode 100644
index 00000000000..97f8cff051f
--- /dev/null
+++ b/spec/fixtures/mail_room_disabled.yml
@@ -0,0 +1,11 @@
+test:
+ incoming_email:
+ enabled: false
+ address: "gitlab-incoming+%{key}@gmail.com"
+ user: "gitlab-incoming@gmail.com"
+ password: "[REDACTED]"
+ host: "imap.gmail.com"
+ port: 993
+ ssl: true
+ start_tls: false
+ mailbox: "inbox"
diff --git a/spec/fixtures/mail_room_enabled.yml b/spec/fixtures/mail_room_enabled.yml
new file mode 100644
index 00000000000..9c94649244d
--- /dev/null
+++ b/spec/fixtures/mail_room_enabled.yml
@@ -0,0 +1,11 @@
+test:
+ incoming_email:
+ enabled: true
+ address: "gitlab-incoming+%{key}@gmail.com"
+ user: "gitlab-incoming@gmail.com"
+ password: "[REDACTED]"
+ host: "imap.gmail.com"
+ port: 993
+ ssl: true
+ start_tls: false
+ mailbox: "inbox"
diff --git a/spec/fixtures/markdown.md.erb b/spec/fixtures/markdown.md.erb
index fe6d42acee2..1772cc3f6a4 100644
--- a/spec/fixtures/markdown.md.erb
+++ b/spec/fixtures/markdown.md.erb
@@ -209,7 +209,7 @@ References should be parseable even inside _<%= merge_request.to_reference %>_ e
- Label by ID: <%= simple_label.to_reference %>
- Label by name: <%= Label.reference_prefix %><%= simple_label.name %>
-- Label by name in quotes: <%= label.to_reference(:name) %>
+- Label by name in quotes: <%= label.to_reference(format: :name) %>
- Ignored in code: `<%= simple_label.to_reference %>`
- Ignored in links: [Link to <%= simple_label.to_reference %>](#label-link)
- Link to label by reference: [Label](<%= label.to_reference %>)
diff --git a/spec/helpers/application_helper_spec.rb b/spec/helpers/application_helper_spec.rb
index f6c1005d265..8013b31524f 100644
--- a/spec/helpers/application_helper_spec.rb
+++ b/spec/helpers/application_helper_spec.rb
@@ -77,7 +77,7 @@ describe ApplicationHelper do
let(:avatar_file_path) { File.join(Rails.root, 'spec', 'fixtures', 'banana_sample.gif') }
it 'should return an url for the avatar' do
- user = create(:user, avatar: File.open(avatar_file_path))
+ user = create(:user, :with_avatar, avatar: File.open(avatar_file_path))
expect(helper.avatar_icon(user.email).to_s).
to match("/uploads/user/avatar/#{user.id}/banana_sample.gif")
@@ -88,7 +88,7 @@ describe ApplicationHelper do
# Must be stubbed after the stub above, and separately
stub_config_setting(url: Settings.send(:build_gitlab_url))
- user = create(:user, avatar: File.open(avatar_file_path))
+ user = create(:user, :with_avatar, avatar: File.open(avatar_file_path))
expect(helper.avatar_icon(user.email).to_s).
to match("/gitlab/uploads/user/avatar/#{user.id}/banana_sample.gif")
@@ -102,7 +102,7 @@ describe ApplicationHelper do
describe 'using a User' do
it 'should return an URL for the avatar' do
- user = create(:user, avatar: File.open(avatar_file_path))
+ user = create(:user, :with_avatar, avatar: File.open(avatar_file_path))
expect(helper.avatar_icon(user).to_s).
to match("/uploads/user/avatar/#{user.id}/banana_sample.gif")
diff --git a/spec/helpers/diff_helper_spec.rb b/spec/helpers/diff_helper_spec.rb
index 14986a74c2e..982c113e84b 100644
--- a/spec/helpers/diff_helper_spec.rb
+++ b/spec/helpers/diff_helper_spec.rb
@@ -22,65 +22,14 @@ describe DiffHelper do
end
end
- describe 'allowed_diff_size' do
+ describe 'diff_options' do
it 'should return hard limit for a diff if force diff is true' do
allow(controller).to receive(:params) { { force_show_diff: true } }
- expect(allowed_diff_size).to eq(1000)
+ expect(diff_options).to include(Commit.max_diff_options)
end
it 'should return safe limit for a diff if force diff is false' do
- expect(allowed_diff_size).to eq(100)
- end
- end
-
- describe 'allowed_diff_lines' do
- it 'should return hard limit for number of lines in a diff if force diff is true' do
- allow(controller).to receive(:params) { { force_show_diff: true } }
- expect(allowed_diff_lines).to eq(50000)
- end
-
- it 'should return safe limit for numbers of lines a diff if force diff is false' do
- expect(allowed_diff_lines).to eq(5000)
- end
- end
-
- describe 'safe_diff_files' do
- it 'should return all files from a commit that is smaller than safe limits' do
- expect(safe_diff_files(diffs, diff_refs).length).to eq(2)
- end
-
- it 'should return only the first file if the diff line count in the 2nd file takes the total beyond safe limits' do
- allow(diffs[1].diff).to receive(:lines).and_return([""] * 4999) #simulate 4999 lines
- expect(safe_diff_files(diffs, diff_refs).length).to eq(1)
- end
-
- it 'should return all files from a commit that is beyond safe limit for numbers of lines if force diff is true' do
- allow(controller).to receive(:params) { { force_show_diff: true } }
- allow(diffs[1].diff).to receive(:lines).and_return([""] * 4999) #simulate 4999 lines
- expect(safe_diff_files(diffs, diff_refs).length).to eq(2)
- end
-
- it 'should return only the first file if the diff line count in the 2nd file takes the total beyond hard limits' do
- allow(controller).to receive(:params) { { force_show_diff: true } }
- allow(diffs[1].diff).to receive(:lines).and_return([""] * 49999) #simulate 49999 lines
- expect(safe_diff_files(diffs, diff_refs).length).to eq(1)
- end
-
- it 'should return only a safe number of file diffs if a commit touches more files than the safe limits' do
- large_diffs = diffs * 100 #simulate 200 diffs
- expect(safe_diff_files(large_diffs, diff_refs).length).to eq(100)
- end
-
- it 'should return all file diffs if a commit touches more files than the safe limits but force diff is true' do
- allow(controller).to receive(:params) { { force_show_diff: true } }
- large_diffs = diffs * 100 #simulate 200 diffs
- expect(safe_diff_files(large_diffs, diff_refs).length).to eq(200)
- end
-
- it 'should return a limited file diffs if a commit touches more files than the hard limits and force diff is true' do
- allow(controller).to receive(:params) { { force_show_diff: true } }
- very_large_diffs = diffs * 1000 #simulate 2000 diffs
- expect(safe_diff_files(very_large_diffs, diff_refs).length).to eq(1000)
+ expect(diff_options).not_to include(:max_lines, :max_files)
end
end
diff --git a/spec/helpers/gitlab_markdown_helper_spec.rb b/spec/helpers/gitlab_markdown_helper_spec.rb
index 9a05b21335c..9adcd916ced 100644
--- a/spec/helpers/gitlab_markdown_helper_spec.rb
+++ b/spec/helpers/gitlab_markdown_helper_spec.rb
@@ -113,7 +113,7 @@ describe GitlabMarkdownHelper do
it 'should replace commit message with emoji to link' do
actual = link_to_gfm(':book:Book', '/foo')
expect(actual).
- to eq %Q(<img class="emoji" title=":book:" alt=":book:" src="http://localhost/assets/emoji/1F4D6.png" height="20" width="20" align="absmiddle"><a href="/foo">Book</a>)
+ to eq %Q(<img class="emoji" title=":book:" alt=":book:" src="http://localhost/assets/1F4D6.png" height="20" width="20" align="absmiddle"><a href="/foo">Book</a>)
end
end
diff --git a/spec/javascripts/fixtures/behaviors/quick_submit.html.haml b/spec/javascripts/fixtures/behaviors/quick_submit.html.haml
index b80a28a33ea..e3788bee813 100644
--- a/spec/javascripts/fixtures/behaviors/quick_submit.html.haml
+++ b/spec/javascripts/fixtures/behaviors/quick_submit.html.haml
@@ -1,6 +1,6 @@
-%form{ action: '/foo' }
- %input.js-quick-submit{ type: 'text' }
- %textarea.js-quick-submit
+%form.js-quick-submit{ action: '/foo' }
+ %input{ type: 'text' }
+ %textarea
%input{ type: 'submit'} Submit
%button.btn{ type: 'submit' } Submit
diff --git a/spec/lib/banzai/filter/emoji_filter_spec.rb b/spec/lib/banzai/filter/emoji_filter_spec.rb
index cf314058158..b5b38cf0c8c 100644
--- a/spec/lib/banzai/filter/emoji_filter_spec.rb
+++ b/spec/lib/banzai/filter/emoji_filter_spec.rb
@@ -14,7 +14,7 @@ describe Banzai::Filter::EmojiFilter, lib: true do
it 'replaces supported emoji' do
doc = filter('<p>:heart:</p>')
- expect(doc.css('img').first.attr('src')).to eq 'https://foo.com/assets/emoji/2764.png'
+ expect(doc.css('img').first.attr('src')).to eq 'https://foo.com/assets/2764.png'
end
it 'ignores unsupported emoji' do
@@ -25,7 +25,7 @@ describe Banzai::Filter::EmojiFilter, lib: true do
it 'correctly encodes the URL' do
doc = filter('<p>:+1:</p>')
- expect(doc.css('img').first.attr('src')).to eq 'https://foo.com/assets/emoji/1F44D.png'
+ expect(doc.css('img').first.attr('src')).to eq 'https://foo.com/assets/1F44D.png'
end
it 'matches at the start of a string' do
diff --git a/spec/lib/banzai/filter/gollum_tags_filter_spec.rb b/spec/lib/banzai/filter/gollum_tags_filter_spec.rb
index 38baa819957..5e23c5c319a 100644
--- a/spec/lib/banzai/filter/gollum_tags_filter_spec.rb
+++ b/spec/lib/banzai/filter/gollum_tags_filter_spec.rb
@@ -86,4 +86,19 @@ describe Banzai::Filter::GollumTagsFilter, lib: true do
expect(doc.at_css('a')['href']).to eq 'wiki-slug'
end
end
+
+ context 'table of contents' do
+ it 'replaces [[<em>TOC</em>]] with ToC result' do
+ doc = described_class.call("<p>[[<em>TOC</em>]]</p>", { project_wiki: project_wiki }, { toc: "FOO" })
+
+ expect(doc.to_html).to eq("FOO")
+ end
+
+ it 'handles an empty ToC result' do
+ input = "<p>[[<em>TOC</em>]]</p>"
+ doc = described_class.call(input, project_wiki: project_wiki)
+
+ expect(doc.to_html).to eq ''
+ end
+ end
end
diff --git a/spec/lib/banzai/filter/label_reference_filter_spec.rb b/spec/lib/banzai/filter/label_reference_filter_spec.rb
index b46ccc47605..e2d21f53b7e 100644
--- a/spec/lib/banzai/filter/label_reference_filter_spec.rb
+++ b/spec/lib/banzai/filter/label_reference_filter_spec.rb
@@ -111,7 +111,7 @@ describe Banzai::Filter::LabelReferenceFilter, lib: true do
context 'String-based multi-word references in quotes' do
let(:label) { create(:label, name: 'gfm references', project: project) }
- let(:reference) { label.to_reference(:name) }
+ let(:reference) { label.to_reference(format: :name) }
it 'links to a valid reference' do
doc = reference_filter("See #{reference}")
@@ -176,4 +176,29 @@ describe Banzai::Filter::LabelReferenceFilter, lib: true do
expect(result[:references][:label]).to eq [label]
end
end
+
+ describe 'cross project label references' do
+ let(:another_project) { create(:empty_project, :public) }
+ let(:project_name) { another_project.name_with_namespace }
+ let(:label) { create(:label, project: another_project, color: '#00ff00') }
+ let(:reference) { label.to_reference(project) }
+
+ let!(:result) { reference_filter("See #{reference}") }
+
+ it 'points to referenced project issues page' do
+ expect(result.css('a').first.attr('href'))
+ .to eq urls.namespace_project_issues_url(another_project.namespace,
+ another_project,
+ label_name: label.name)
+ end
+
+ it 'has valid color' do
+ expect(result.css('a span').first.attr('style'))
+ .to match /background-color: #00ff00/
+ end
+
+ it 'contains cross project content' do
+ expect(result.css('a').first.text).to eq "#{label.name} in #{project_name}"
+ end
+ end
end
diff --git a/spec/lib/banzai/filter/sanitization_filter_spec.rb b/spec/lib/banzai/filter/sanitization_filter_spec.rb
index e14a6dbf922..4a7b00c7660 100644
--- a/spec/lib/banzai/filter/sanitization_filter_spec.rb
+++ b/spec/lib/banzai/filter/sanitization_filter_spec.rb
@@ -156,13 +156,27 @@ describe Banzai::Filter::SanitizationFilter, lib: true do
}
protocols.each do |name, data|
- it "handles #{name}" do
+ it "disallows #{name}" do
doc = filter(data[:input])
expect(doc.to_html).to eq data[:output]
end
end
+ it 'disallows data links' do
+ input = '<a href="data:text/html;base64,PHNjcmlwdD5hbGVydCgnWFNTJyk8L3NjcmlwdD4K">XSS</a>'
+ output = filter(input)
+
+ expect(output.to_html).to eq '<a>XSS</a>'
+ end
+
+ it 'disallows vbscript links' do
+ input = '<a href="vbscript:alert(document.domain)">XSS</a>'
+ output = filter(input)
+
+ expect(output.to_html).to eq '<a>XSS</a>'
+ end
+
it 'allows non-standard anchor schemes' do
exp = %q{<a href="irc://irc.freenode.net/git">IRC</a>}
act = filter(exp)
diff --git a/spec/lib/banzai/filter/yaml_front_matter_filter_spec.rb b/spec/lib/banzai/filter/yaml_front_matter_filter_spec.rb
new file mode 100644
index 00000000000..fe70eada7eb
--- /dev/null
+++ b/spec/lib/banzai/filter/yaml_front_matter_filter_spec.rb
@@ -0,0 +1,53 @@
+require 'rails_helper'
+
+describe Banzai::Filter::YamlFrontMatterFilter, lib: true do
+ include FilterSpecHelper
+
+ it 'allows for `encoding:` before the frontmatter' do
+ content = <<-MD.strip_heredoc
+ # encoding: UTF-8
+ ---
+ foo: foo
+ ---
+
+ # Header
+
+ Content
+ MD
+
+ output = filter(content)
+
+ expect(output).not_to match 'encoding'
+ end
+
+ it 'converts YAML frontmatter to a fenced code block' do
+ content = <<-MD.strip_heredoc
+ ---
+ bar: :bar_symbol
+ ---
+
+ # Header
+
+ Content
+ MD
+
+ output = filter(content)
+
+ aggregate_failures do
+ expect(output).not_to include '---'
+ expect(output).to include "```yaml\nbar: :bar_symbol\n```"
+ end
+ end
+
+ context 'on content without frontmatter' do
+ it 'returns the content unmodified' do
+ content = <<-MD.strip_heredoc
+ # This is some Markdown
+
+ It has no YAML frontmatter to parse.
+ MD
+
+ expect(filter(content)).to eq content
+ end
+ end
+end
diff --git a/spec/lib/banzai/filter_array_spec.rb b/spec/lib/banzai/filter_array_spec.rb
new file mode 100644
index 00000000000..ea84005e7f8
--- /dev/null
+++ b/spec/lib/banzai/filter_array_spec.rb
@@ -0,0 +1,39 @@
+require 'spec_helper'
+
+describe Banzai::FilterArray do
+ describe '#insert_after' do
+ it 'inserts an element after a provided element' do
+ filters = described_class.new(%w(a b c))
+
+ filters.insert_after('b', '1')
+
+ expect(filters).to eq %w(a b 1 c)
+ end
+
+ it 'inserts an element at the end when the provided element does not exist' do
+ filters = described_class.new(%w(a b c))
+
+ filters.insert_after('d', '1')
+
+ expect(filters).to eq %w(a b c 1)
+ end
+ end
+
+ describe '#insert_before' do
+ it 'inserts an element before a provided element' do
+ filters = described_class.new(%w(a b c))
+
+ filters.insert_before('b', '1')
+
+ expect(filters).to eq %w(a 1 b c)
+ end
+
+ it 'inserts an element at the beginning when the provided element does not exist' do
+ filters = described_class.new(%w(a b c))
+
+ filters.insert_before('d', '1')
+
+ expect(filters).to eq %w(1 a b c)
+ end
+ end
+end
diff --git a/spec/lib/banzai/pipeline/wiki_pipeline_spec.rb b/spec/lib/banzai/pipeline/wiki_pipeline_spec.rb
new file mode 100644
index 00000000000..3e25406e498
--- /dev/null
+++ b/spec/lib/banzai/pipeline/wiki_pipeline_spec.rb
@@ -0,0 +1,53 @@
+require 'rails_helper'
+
+describe Banzai::Pipeline::WikiPipeline do
+ describe 'TableOfContents' do
+ it 'replaces the tag with the TableOfContentsFilter result' do
+ markdown = <<-MD.strip_heredoc
+ [[_TOC_]]
+
+ ## Header
+
+ Foo
+ MD
+
+ result = described_class.call(markdown, project: spy, project_wiki: double)
+
+ aggregate_failures do
+ expect(result[:output].text).not_to include '[['
+ expect(result[:output].text).not_to include 'TOC'
+ expect(result[:output].to_html).to include(result[:toc])
+ end
+ end
+
+ it 'is case-sensitive' do
+ markdown = <<-MD.strip_heredoc
+ [[_toc_]]
+
+ # Header 1
+
+ Foo
+ MD
+
+ output = described_class.to_html(markdown, project: spy, project_wiki: double)
+
+ expect(output).to include('[[<em>toc</em>]]')
+ end
+
+ it 'handles an empty pipeline result' do
+ # No Markdown headers in this doc, so `result[:toc]` will be empty
+ markdown = <<-MD.strip_heredoc
+ [[_TOC_]]
+
+ Foo
+ MD
+
+ output = described_class.to_html(markdown, project: spy, project_wiki: double)
+
+ aggregate_failures do
+ expect(output).not_to include('<ul>')
+ expect(output).not_to include('[[<em>TOC</em>]]')
+ end
+ end
+ end
+end
diff --git a/spec/lib/ci/status_spec.rb b/spec/lib/ci/status_spec.rb
new file mode 100644
index 00000000000..1539720bb8d
--- /dev/null
+++ b/spec/lib/ci/status_spec.rb
@@ -0,0 +1,71 @@
+require 'spec_helper'
+
+describe Ci::Status do
+ describe '.get_status' do
+ subject { described_class.get_status(statuses) }
+
+ shared_examples 'build status summary' do
+ context 'all successful' do
+ let(:statuses) { Array.new(2) { create(type, status: :success) } }
+ it { is_expected.to eq 'success' }
+ end
+
+ context 'at least one failed' do
+ let(:statuses) do
+ [create(type, status: :success), create(type, status: :failed)]
+ end
+
+ it { is_expected.to eq 'failed' }
+ end
+
+ context 'at least one running' do
+ let(:statuses) do
+ [create(type, status: :success), create(type, status: :running)]
+ end
+
+ it { is_expected.to eq 'running' }
+ end
+
+ context 'at least one pending' do
+ let(:statuses) do
+ [create(type, status: :success), create(type, status: :pending)]
+ end
+
+ it { is_expected.to eq 'running' }
+ end
+
+ context 'success and failed but allowed to fail' do
+ let(:statuses) do
+ [create(type, status: :success),
+ create(type, status: :failed, allow_failure: true)]
+ end
+
+ it { is_expected.to eq 'success' }
+ end
+
+ context 'one failed but allowed to fail' do
+ let(:statuses) { [create(type, status: :failed, allow_failure: true)] }
+ it { is_expected.to eq 'success' }
+ end
+
+ context 'one finished and second running but allowed to fail' do
+ let(:statuses) do
+ [create(type, status: :success),
+ create(type, status: :running, allow_failure: true)]
+ end
+
+ it { is_expected.to eq 'running' }
+ end
+ end
+
+ context 'ci build statuses' do
+ let(:type) { :ci_build }
+ it_behaves_like 'build status summary'
+ end
+
+ context 'generic commit statuses' do
+ let(:type) { :generic_commit_status }
+ it_behaves_like 'build status summary'
+ end
+ end
+end
diff --git a/spec/lib/gitlab/database_spec.rb b/spec/lib/gitlab/database_spec.rb
index bd8688fefa1..d0a447753b7 100644
--- a/spec/lib/gitlab/database_spec.rb
+++ b/spec/lib/gitlab/database_spec.rb
@@ -1,5 +1,9 @@
require 'spec_helper'
+class MigrationTest
+ include Gitlab::Database
+end
+
describe Gitlab::Database, lib: true do
# These are just simple smoke tests to check if the methods work (regardless
# of what they may return).
@@ -34,4 +38,32 @@ describe Gitlab::Database, lib: true do
end
end
end
+
+ describe '#true_value' do
+ it 'returns correct value for PostgreSQL' do
+ expect(described_class).to receive(:postgresql?).and_return(true)
+
+ expect(MigrationTest.new.true_value).to eq "'t'"
+ end
+
+ it 'returns correct value for MySQL' do
+ expect(described_class).to receive(:postgresql?).and_return(false)
+
+ expect(MigrationTest.new.true_value).to eq 1
+ end
+ end
+
+ describe '#false_value' do
+ it 'returns correct value for PostgreSQL' do
+ expect(described_class).to receive(:postgresql?).and_return(true)
+
+ expect(MigrationTest.new.false_value).to eq "'f'"
+ end
+
+ it 'returns correct value for MySQL' do
+ expect(described_class).to receive(:postgresql?).and_return(false)
+
+ expect(MigrationTest.new.false_value).to eq 0
+ end
+ end
end
diff --git a/spec/lib/gitlab/diff/parser_spec.rb b/spec/lib/gitlab/diff/parser_spec.rb
index fe0dea77909..f576c39284e 100644
--- a/spec/lib/gitlab/diff/parser_spec.rb
+++ b/spec/lib/gitlab/diff/parser_spec.rb
@@ -47,7 +47,7 @@ eos
end
before do
- @lines = parser.parse(diff.lines)
+ @lines = parser.parse(diff.lines).to_a
end
it { expect(@lines.size).to eq(30) }
diff --git a/spec/lib/gitlab/email/message/repository_push_spec.rb b/spec/lib/gitlab/email/message/repository_push_spec.rb
index 56ae2a8d121..b2d7a799810 100644
--- a/spec/lib/gitlab/email/message/repository_push_spec.rb
+++ b/spec/lib/gitlab/email/message/repository_push_spec.rb
@@ -72,7 +72,7 @@ describe Gitlab::Email::Message::RepositoryPush do
describe '#compare_timeout' do
subject { message.compare_timeout }
- it { is_expected.to eq compare.timeout }
+ it { is_expected.to eq compare.diffs.overflow? }
end
describe '#reverse_compare?' do
diff --git a/spec/lib/gitlab/note_data_builder_spec.rb b/spec/lib/gitlab/note_data_builder_spec.rb
index 691f36e6cb7..da652677443 100644
--- a/spec/lib/gitlab/note_data_builder_spec.rb
+++ b/spec/lib/gitlab/note_data_builder_spec.rb
@@ -16,62 +16,80 @@ describe 'Gitlab::NoteDataBuilder', lib: true do
end
describe 'When asking for a note on commit' do
- let(:note) { create(:note_on_commit) }
+ let(:note) { create(:note_on_commit, project: project) }
it 'returns the note and commit-specific data' do
expect(data).to have_key(:commit)
end
+
+ include_examples 'project hook data'
+ include_examples 'deprecated repository hook data'
end
describe 'When asking for a note on commit diff' do
- let(:note) { create(:note_on_commit_diff) }
+ let(:note) { create(:note_on_commit_diff, project: project) }
it 'returns the note and commit-specific data' do
expect(data).to have_key(:commit)
end
+
+ include_examples 'project hook data'
+ include_examples 'deprecated repository hook data'
end
describe 'When asking for a note on issue' do
let(:issue) { create(:issue, created_at: fixed_time, updated_at: fixed_time) }
- let(:note) { create(:note_on_issue, noteable_id: issue.id) }
+ let(:note) { create(:note_on_issue, noteable_id: issue.id, project: project) }
it 'returns the note and issue-specific data' do
expect(data).to have_key(:issue)
expect(data[:issue].except('updated_at')).to eq(issue.hook_attrs.except('updated_at'))
expect(data[:issue]['updated_at']).to be > issue.hook_attrs['updated_at']
end
+
+ include_examples 'project hook data'
+ include_examples 'deprecated repository hook data'
end
describe 'When asking for a note on merge request' do
let(:merge_request) { create(:merge_request, created_at: fixed_time, updated_at: fixed_time) }
- let(:note) { create(:note_on_merge_request, noteable_id: merge_request.id) }
+ let(:note) { create(:note_on_merge_request, noteable_id: merge_request.id, project: project) }
it 'returns the note and merge request data' do
expect(data).to have_key(:merge_request)
expect(data[:merge_request].except('updated_at')).to eq(merge_request.hook_attrs.except('updated_at'))
expect(data[:merge_request]['updated_at']).to be > merge_request.hook_attrs['updated_at']
end
+
+ include_examples 'project hook data'
+ include_examples 'deprecated repository hook data'
end
describe 'When asking for a note on merge request diff' do
let(:merge_request) { create(:merge_request, created_at: fixed_time, updated_at: fixed_time) }
- let(:note) { create(:note_on_merge_request_diff, noteable_id: merge_request.id) }
+ let(:note) { create(:note_on_merge_request_diff, noteable_id: merge_request.id, project: project) }
it 'returns the note and merge request diff data' do
expect(data).to have_key(:merge_request)
expect(data[:merge_request].except('updated_at')).to eq(merge_request.hook_attrs.except('updated_at'))
expect(data[:merge_request]['updated_at']).to be > merge_request.hook_attrs['updated_at']
end
+
+ include_examples 'project hook data'
+ include_examples 'deprecated repository hook data'
end
describe 'When asking for a note on project snippet' do
let!(:snippet) { create(:project_snippet, created_at: fixed_time, updated_at: fixed_time) }
- let!(:note) { create(:note_on_project_snippet, noteable_id: snippet.id) }
+ let!(:note) { create(:note_on_project_snippet, noteable_id: snippet.id, project: project) }
it 'returns the note and project snippet data' do
expect(data).to have_key(:snippet)
expect(data[:snippet].except('updated_at')).to eq(snippet.hook_attrs.except('updated_at'))
expect(data[:snippet]['updated_at']).to be > snippet.hook_attrs['updated_at']
end
+
+ include_examples 'project hook data'
+ include_examples 'deprecated repository hook data'
end
end
diff --git a/spec/lib/gitlab/o_auth/user_spec.rb b/spec/lib/gitlab/o_auth/user_spec.rb
index 925bc442a90..3a769acfdc0 100644
--- a/spec/lib/gitlab/o_auth/user_spec.rb
+++ b/spec/lib/gitlab/o_auth/user_spec.rb
@@ -41,7 +41,20 @@ describe Gitlab::OAuth::User, lib: true do
describe 'signup' do
shared_examples "to verify compliance with allow_single_sign_on" do
- context "with allow_single_sign_on enabled" do
+ context "with new allow_single_sign_on enabled syntax" do
+ before { stub_omniauth_config(allow_single_sign_on: ['twitter']) }
+
+ it "creates a user from Omniauth" do
+ oauth_user.save
+
+ expect(gl_user).to be_valid
+ identity = gl_user.identities.first
+ expect(identity.extern_uid).to eql uid
+ expect(identity.provider).to eql 'twitter'
+ end
+ end
+
+ context "with old allow_single_sign_on enabled syntax" do
before { stub_omniauth_config(allow_single_sign_on: true) }
it "creates a user from Omniauth" do
@@ -54,7 +67,14 @@ describe Gitlab::OAuth::User, lib: true do
end
end
- context "with allow_single_sign_on disabled (Default)" do
+ context "with new allow_single_sign_on disabled syntax" do
+ before { stub_omniauth_config(allow_single_sign_on: []) }
+ it "throws an error" do
+ expect{ oauth_user.save }.to raise_error StandardError
+ end
+ end
+
+ context "with old allow_single_sign_on disabled (Default)" do
before { stub_omniauth_config(allow_single_sign_on: false) }
it "throws an error" do
expect{ oauth_user.save }.to raise_error StandardError
@@ -135,7 +155,7 @@ describe Gitlab::OAuth::User, lib: true do
describe 'blocking' do
let(:provider) { 'twitter' }
- before { stub_omniauth_config(allow_single_sign_on: true) }
+ before { stub_omniauth_config(allow_single_sign_on: ['twitter']) }
context 'signup with omniauth only' do
context 'dont block on create' do
diff --git a/spec/lib/gitlab/push_data_builder_spec.rb b/spec/lib/gitlab/push_data_builder_spec.rb
index 3ef61685398..961022b9d12 100644
--- a/spec/lib/gitlab/push_data_builder_spec.rb
+++ b/spec/lib/gitlab/push_data_builder_spec.rb
@@ -1,34 +1,32 @@
require 'spec_helper'
-describe 'Gitlab::PushDataBuilder', lib: true do
+describe Gitlab::PushDataBuilder, lib: true do
let(:project) { create(:project) }
let(:user) { create(:user) }
- describe :build_sample do
- let(:data) { Gitlab::PushDataBuilder.build_sample(project, user) }
+ describe '.build_sample' do
+ let(:data) { described_class.build_sample(project, user) }
it { expect(data).to be_a(Hash) }
it { expect(data[:before]).to eq('6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9') }
it { expect(data[:after]).to eq('5937ac0a7beb003549fc5fd26fc247adbce4a52e') }
it { expect(data[:ref]).to eq('refs/heads/master') }
it { expect(data[:commits].size).to eq(3) }
- it { expect(data[:repository][:git_http_url]).to eq(project.http_url_to_repo) }
- it { expect(data[:repository][:git_ssh_url]).to eq(project.ssh_url_to_repo) }
- it { expect(data[:repository][:visibility_level]).to eq(project.visibility_level) }
it { expect(data[:total_commits_count]).to eq(3) }
it { expect(data[:commits].first[:added]).to eq(["gitlab-grack"]) }
it { expect(data[:commits].first[:modified]).to eq([".gitmodules"]) }
it { expect(data[:commits].first[:removed]).to eq([]) }
+
+ include_examples 'project hook data'
+ include_examples 'deprecated repository hook data'
end
- describe :build do
+ describe '.build' do
let(:data) do
- Gitlab::PushDataBuilder.build(project,
- user,
- Gitlab::Git::BLANK_SHA,
- '8a2a6eb295bb170b34c24c76c49ed0e9b2eaf34b',
- 'refs/tags/v1.1.0')
+ described_class.build(project, user, Gitlab::Git::BLANK_SHA,
+ '8a2a6eb295bb170b34c24c76c49ed0e9b2eaf34b',
+ 'refs/tags/v1.1.0')
end
it { expect(data).to be_a(Hash) }
@@ -38,5 +36,10 @@ describe 'Gitlab::PushDataBuilder', lib: true do
it { expect(data[:ref]).to eq('refs/tags/v1.1.0') }
it { expect(data[:commits]).to be_empty }
it { expect(data[:total_commits_count]).to be_zero }
+
+ it 'does not raise an error when given nil commits' do
+ expect { described_class.build(spy, spy, spy, spy, spy, nil) }.
+ not_to raise_error
+ end
end
end
diff --git a/spec/lib/gitlab/saml/user_spec.rb b/spec/lib/gitlab/saml/user_spec.rb
new file mode 100644
index 00000000000..de7cd99d49d
--- /dev/null
+++ b/spec/lib/gitlab/saml/user_spec.rb
@@ -0,0 +1,271 @@
+require 'spec_helper'
+
+describe Gitlab::Saml::User, lib: true do
+ let(:saml_user) { described_class.new(auth_hash) }
+ let(:gl_user) { saml_user.gl_user }
+ let(:uid) { 'my-uid' }
+ let(:provider) { 'saml' }
+ let(:auth_hash) { OmniAuth::AuthHash.new(uid: uid, provider: provider, info: info_hash) }
+ let(:info_hash) do
+ {
+ name: 'John',
+ email: 'john@mail.com'
+ }
+ end
+ let(:ldap_user) { Gitlab::LDAP::Person.new(Net::LDAP::Entry.new, 'ldapmain') }
+
+ describe '#save' do
+ def stub_omniauth_config(messages)
+ allow(Gitlab.config.omniauth).to receive_messages(messages)
+ end
+
+ def stub_ldap_config(messages)
+ allow(Gitlab::LDAP::Config).to receive_messages(messages)
+ end
+
+ describe 'account exists on server' do
+ before { stub_omniauth_config({ allow_single_sign_on: ['saml'], auto_link_saml_user: true }) }
+ context 'and should bind with SAML' do
+ let!(:existing_user) { create(:user, email: 'john@mail.com', username: 'john') }
+ it 'adds the SAML identity to the existing user' do
+ saml_user.save
+ expect(gl_user).to be_valid
+ expect(gl_user).to eq existing_user
+ identity = gl_user.identities.first
+ expect(identity.extern_uid).to eql uid
+ expect(identity.provider).to eql 'saml'
+ end
+ end
+ end
+
+ describe 'no account exists on server' do
+ shared_examples 'to verify compliance with allow_single_sign_on' do
+ context 'with allow_single_sign_on enabled' do
+ before { stub_omniauth_config(allow_single_sign_on: ['saml']) }
+
+ it 'creates a user from SAML' do
+ saml_user.save
+
+ expect(gl_user).to be_valid
+ identity = gl_user.identities.first
+ expect(identity.extern_uid).to eql uid
+ expect(identity.provider).to eql 'saml'
+ end
+ end
+
+ context 'with allow_single_sign_on default (["saml"])' do
+ before { stub_omniauth_config(allow_single_sign_on: ['saml']) }
+ it 'should not throw an error' do
+ expect{ saml_user.save }.not_to raise_error
+ end
+ end
+
+ context 'with allow_single_sign_on disabled' do
+ before { stub_omniauth_config(allow_single_sign_on: false) }
+ it 'should throw an error' do
+ expect{ saml_user.save }.to raise_error StandardError
+ end
+ end
+ end
+
+ context 'with auto_link_ldap_user disabled (default)' do
+ before { stub_omniauth_config({ auto_link_ldap_user: false, auto_link_saml_user: false, allow_single_sign_on: ['saml'] }) }
+ include_examples 'to verify compliance with allow_single_sign_on'
+ end
+
+ context 'with auto_link_ldap_user enabled' do
+ before { stub_omniauth_config({ auto_link_ldap_user: true, auto_link_saml_user: false }) }
+
+ context 'and no LDAP provider defined' do
+ before { stub_ldap_config(providers: []) }
+
+ include_examples 'to verify compliance with allow_single_sign_on'
+ end
+
+ context 'and at least one LDAP provider is defined' do
+ before { stub_ldap_config(providers: %w(ldapmain)) }
+
+ context 'and a corresponding LDAP person' do
+ before do
+ allow(ldap_user).to receive(:uid) { uid }
+ allow(ldap_user).to receive(:username) { uid }
+ allow(ldap_user).to receive(:email) { ['johndoe@example.com','john2@example.com'] }
+ allow(ldap_user).to receive(:dn) { 'uid=user1,ou=People,dc=example' }
+ allow(Gitlab::LDAP::Person).to receive(:find_by_uid).and_return(ldap_user)
+ end
+
+ context 'and no account for the LDAP user' do
+
+ it 'creates a user with dual LDAP and SAML identities' do
+ saml_user.save
+
+ expect(gl_user).to be_valid
+ expect(gl_user.username).to eql uid
+ expect(gl_user.email).to eql 'johndoe@example.com'
+ expect(gl_user.identities.length).to eql 2
+ identities_as_hash = gl_user.identities.map { |id| { provider: id.provider, extern_uid: id.extern_uid } }
+ expect(identities_as_hash).to match_array([ { provider: 'ldapmain', extern_uid: 'uid=user1,ou=People,dc=example' },
+ { provider: 'saml', extern_uid: uid }
+ ])
+ end
+ end
+
+ context 'and LDAP user has an account already' do
+ let!(:existing_user) { create(:omniauth_user, email: 'john@example.com', extern_uid: 'uid=user1,ou=People,dc=example', provider: 'ldapmain', username: 'john') }
+ it "adds the omniauth identity to the LDAP account" do
+ saml_user.save
+
+ expect(gl_user).to be_valid
+ expect(gl_user.username).to eql 'john'
+ expect(gl_user.email).to eql 'john@example.com'
+ expect(gl_user.identities.length).to eql 2
+ identities_as_hash = gl_user.identities.map { |id| { provider: id.provider, extern_uid: id.extern_uid } }
+ expect(identities_as_hash).to match_array([ { provider: 'ldapmain', extern_uid: 'uid=user1,ou=People,dc=example' },
+ { provider: 'saml', extern_uid: uid }
+ ])
+ end
+ end
+ end
+
+ context 'and no corresponding LDAP person' do
+ before { allow(Gitlab::LDAP::Person).to receive(:find_by_uid).and_return(nil) }
+
+ include_examples 'to verify compliance with allow_single_sign_on'
+ end
+ end
+ end
+
+ end
+
+ describe 'blocking' do
+ before { stub_omniauth_config({ allow_saml_sign_up: true, auto_link_saml_user: true }) }
+
+ context 'signup with SAML only' do
+ context 'dont block on create' do
+ before { stub_omniauth_config(block_auto_created_users: false) }
+
+ it 'should not block the user' do
+ saml_user.save
+ expect(gl_user).to be_valid
+ expect(gl_user).not_to be_blocked
+ end
+ end
+
+ context 'block on create' do
+ before { stub_omniauth_config(block_auto_created_users: true) }
+
+ it 'should block user' do
+ saml_user.save
+ expect(gl_user).to be_valid
+ expect(gl_user).to be_blocked
+ end
+ end
+ end
+
+ context 'signup with linked omniauth and LDAP account' do
+ before do
+ stub_omniauth_config(auto_link_ldap_user: true)
+ allow(ldap_user).to receive(:uid) { uid }
+ allow(ldap_user).to receive(:username) { uid }
+ allow(ldap_user).to receive(:email) { ['johndoe@example.com','john2@example.com'] }
+ allow(ldap_user).to receive(:dn) { 'uid=user1,ou=People,dc=example' }
+ allow(saml_user).to receive(:ldap_person).and_return(ldap_user)
+ end
+
+ context "and no account for the LDAP user" do
+ context 'dont block on create (LDAP)' do
+ before { allow_any_instance_of(Gitlab::LDAP::Config).to receive_messages(block_auto_created_users: false) }
+
+ it do
+ saml_user.save
+ expect(gl_user).to be_valid
+ expect(gl_user).not_to be_blocked
+ end
+ end
+
+ context 'block on create (LDAP)' do
+ before { allow_any_instance_of(Gitlab::LDAP::Config).to receive_messages(block_auto_created_users: true) }
+
+ it do
+ saml_user.save
+ expect(gl_user).to be_valid
+ expect(gl_user).to be_blocked
+ end
+ end
+ end
+
+ context 'and LDAP user has an account already' do
+ let!(:existing_user) { create(:omniauth_user, email: 'john@example.com', extern_uid: 'uid=user1,ou=People,dc=example', provider: 'ldapmain', username: 'john') }
+
+ context 'dont block on create (LDAP)' do
+ before { allow_any_instance_of(Gitlab::LDAP::Config).to receive_messages(block_auto_created_users: false) }
+
+ it do
+ saml_user.save
+ expect(gl_user).to be_valid
+ expect(gl_user).not_to be_blocked
+ end
+ end
+
+ context 'block on create (LDAP)' do
+ before { allow_any_instance_of(Gitlab::LDAP::Config).to receive_messages(block_auto_created_users: true) }
+
+ it do
+ saml_user.save
+ expect(gl_user).to be_valid
+ expect(gl_user).not_to be_blocked
+ end
+ end
+ end
+ end
+
+
+ context 'sign-in' do
+ before do
+ saml_user.save
+ saml_user.gl_user.activate
+ end
+
+ context 'dont block on create' do
+ before { stub_omniauth_config(block_auto_created_users: false) }
+
+ it do
+ saml_user.save
+ expect(gl_user).to be_valid
+ expect(gl_user).not_to be_blocked
+ end
+ end
+
+ context 'block on create' do
+ before { stub_omniauth_config(block_auto_created_users: true) }
+
+ it do
+ saml_user.save
+ expect(gl_user).to be_valid
+ expect(gl_user).not_to be_blocked
+ end
+ end
+
+ context 'dont block on create (LDAP)' do
+ before { allow_any_instance_of(Gitlab::LDAP::Config).to receive_messages(block_auto_created_users: false) }
+
+ it do
+ saml_user.save
+ expect(gl_user).to be_valid
+ expect(gl_user).not_to be_blocked
+ end
+ end
+
+ context 'block on create (LDAP)' do
+ before { allow_any_instance_of(Gitlab::LDAP::Config).to receive_messages(block_auto_created_users: true) }
+
+ it do
+ saml_user.save
+ expect(gl_user).to be_valid
+ expect(gl_user).not_to be_blocked
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/spec/lib/gitlab/workhorse_spec.rb b/spec/lib/gitlab/workhorse_spec.rb
new file mode 100644
index 00000000000..d940bf05061
--- /dev/null
+++ b/spec/lib/gitlab/workhorse_spec.rb
@@ -0,0 +1,18 @@
+require 'spec_helper'
+
+describe Gitlab::Workhorse, lib: true do
+ let(:project) { create(:project) }
+ let(:subject) { Gitlab::Workhorse }
+
+ describe "#send_git_archive" do
+ context "when the repository doesn't have an archive file path" do
+ before do
+ allow(project.repository).to receive(:archive_metadata).and_return(Hash.new)
+ end
+
+ it "raises an error" do
+ expect { subject.send_git_archive(project, "master", "zip") }.to raise_error(RuntimeError)
+ end
+ end
+ end
+end
diff --git a/spec/models/appearance_spec.rb b/spec/models/appearance_spec.rb
new file mode 100644
index 00000000000..c5658bd26e1
--- /dev/null
+++ b/spec/models/appearance_spec.rb
@@ -0,0 +1,10 @@
+require 'rails_helper'
+
+RSpec.describe Appearance, type: :model do
+ subject { create(:appearance) }
+
+ it { is_expected.to be_valid }
+
+ it { is_expected.to validate_presence_of(:title) }
+ it { is_expected.to validate_presence_of(:description) }
+end
diff --git a/spec/models/blob_spec.rb b/spec/models/blob_spec.rb
new file mode 100644
index 00000000000..78e95c8fac5
--- /dev/null
+++ b/spec/models/blob_spec.rb
@@ -0,0 +1,81 @@
+require 'rails_helper'
+
+describe Blob do
+ describe '.decorate' do
+ it 'returns NilClass when given nil' do
+ expect(described_class.decorate(nil)).to be_nil
+ end
+ end
+
+ describe '#svg?' do
+ it 'is falsey when not text' do
+ git_blob = double(text?: false)
+
+ expect(described_class.decorate(git_blob)).not_to be_svg
+ end
+
+ it 'is falsey when no language is detected' do
+ git_blob = double(text?: true, language: nil)
+
+ expect(described_class.decorate(git_blob)).not_to be_svg
+ end
+
+ it' is falsey when language is not SVG' do
+ git_blob = double(text?: true, language: double(name: 'XML'))
+
+ expect(described_class.decorate(git_blob)).not_to be_svg
+ end
+
+ it 'is truthy when language is SVG' do
+ git_blob = double(text?: true, language: double(name: 'SVG'))
+
+ expect(described_class.decorate(git_blob)).to be_svg
+ end
+ end
+
+ describe '#to_partial_path' do
+ def stubbed_blob(overrides = {})
+ overrides.reverse_merge!(
+ image?: false,
+ language: nil,
+ lfs_pointer?: false,
+ svg?: false,
+ text?: false
+ )
+
+ described_class.decorate(double).tap do |blob|
+ allow(blob).to receive_messages(overrides)
+ end
+ end
+
+ it 'handles LFS pointers' do
+ blob = stubbed_blob(lfs_pointer?: true)
+
+ expect(blob.to_partial_path).to eq 'download'
+ end
+
+ it 'handles SVGs' do
+ blob = stubbed_blob(text?: true, svg?: true)
+
+ expect(blob.to_partial_path).to eq 'image'
+ end
+
+ it 'handles images' do
+ blob = stubbed_blob(image?: true)
+
+ expect(blob.to_partial_path).to eq 'image'
+ end
+
+ it 'handles text' do
+ blob = stubbed_blob(text?: true)
+
+ expect(blob.to_partial_path).to eq 'text'
+ end
+
+ it 'defaults to download' do
+ blob = stubbed_blob
+
+ expect(blob.to_partial_path).to eq 'download'
+ end
+ end
+end
diff --git a/spec/models/build_spec.rb b/spec/models/build_spec.rb
index 606340d87e4..e3d3d453653 100644
--- a/spec/models/build_spec.rb
+++ b/spec/models/build_spec.rb
@@ -243,7 +243,7 @@ describe Ci::Build, models: true do
end
describe :can_be_served? do
- let(:runner) { FactoryGirl.create :ci_specific_runner }
+ let(:runner) { FactoryGirl.create :ci_runner }
before { build.project.runners << runner }
@@ -285,7 +285,7 @@ describe Ci::Build, models: true do
end
context 'if there are runner' do
- let(:runner) { FactoryGirl.create :ci_specific_runner }
+ let(:runner) { FactoryGirl.create :ci_runner }
before do
build.project.runners << runner
@@ -322,7 +322,7 @@ describe Ci::Build, models: true do
it { is_expected.to be_truthy }
context "and there are specific runner" do
- let(:runner) { FactoryGirl.create :ci_specific_runner, contacted_at: 1.second.ago }
+ let(:runner) { FactoryGirl.create :ci_runner, contacted_at: 1.second.ago }
before do
build.project.runners << runner
@@ -346,15 +346,14 @@ describe Ci::Build, models: true do
describe :artifacts_download_url do
subject { build.artifacts_download_url }
- it "should be nil if artifact doesn't exist" do
- build.update_attributes(artifacts_file: nil)
- is_expected.to be_nil
+ context 'artifacts file does not exist' do
+ before { build.update_attributes(artifacts_file: nil) }
+ it { is_expected.to be_nil }
end
- it 'should not be nil if artifact exist' do
- gif = fixture_file_upload(Rails.root + 'spec/fixtures/banana_sample.gif', 'image/gif')
- build.update_attributes(artifacts_file: gif)
- is_expected.to_not be_nil
+ context 'artifacts file exists' do
+ let(:build) { create(:ci_build, :artifacts) }
+ it { is_expected.to_not be_nil }
end
end
@@ -381,11 +380,7 @@ describe Ci::Build, models: true do
end
context 'artifacts archive exists' do
- before do
- gif = fixture_file_upload(Rails.root + 'spec/fixtures/banana_sample.gif', 'image/gif')
- build.update_attributes(artifacts_file: gif)
- end
-
+ let(:build) { create(:ci_build, :artifacts) }
it { is_expected.to be_truthy }
end
end
@@ -398,16 +393,7 @@ describe Ci::Build, models: true do
end
context 'artifacts archive is a zip file and metadata exists' do
- before do
- fixture_dir = Rails.root + 'spec/fixtures/'
- archive = fixture_file_upload(fixture_dir + 'ci_build_artifacts.zip',
- 'application/zip')
- metadata = fixture_file_upload(fixture_dir + 'ci_build_artifacts_metadata.gz',
- 'application/x-gzip')
- build.update_attributes(artifacts_file: archive)
- build.update_attributes(artifacts_metadata: metadata)
- end
-
+ let(:build) { create(:ci_build, :artifacts) }
it { is_expected.to be_truthy }
end
end
@@ -511,6 +497,103 @@ describe Ci::Build, models: true do
expect(@build2.merge_request.id).to eq(@merge_request.id)
end
end
+ end
+
+ describe 'build erasable' do
+ shared_examples 'erasable' do
+ it 'should remove artifact file' do
+ expect(build.artifacts_file.exists?).to be_falsy
+ end
+
+ it 'should remove artifact metadata file' do
+ expect(build.artifacts_metadata.exists?).to be_falsy
+ end
+
+ it 'should erase build trace in trace file' do
+ expect(build.trace).to be_empty
+ end
+
+ it 'should set erased to true' do
+ expect(build.erased?).to be true
+ end
+
+ it 'should set erase date' do
+ expect(build.erased_at).to_not be_falsy
+ end
+ end
+
+ context 'build is not erasable' do
+ let!(:build) { create(:ci_build) }
+
+ describe '#erase' do
+ subject { build.erase }
+
+ it { is_expected.to be false }
+ end
+
+ describe '#erasable?' do
+ subject { build.erasable? }
+ it { is_expected.to eq false }
+ end
+ end
+
+ context 'build is erasable' do
+ let!(:build) { create(:ci_build, :trace, :success, :artifacts) }
+
+ describe '#erase' do
+ before { build.erase(erased_by: user) }
+
+ context 'erased by user' do
+ let!(:user) { create(:user, username: 'eraser') }
+
+ include_examples 'erasable'
+
+ it 'should record user who erased a build' do
+ expect(build.erased_by).to eq user
+ end
+ end
+
+ context 'erased by system' do
+ let(:user) { nil }
+
+ include_examples 'erasable'
+
+ it 'should not set user who erased a build' do
+ expect(build.erased_by).to be_nil
+ end
+ end
+ end
+ describe '#erasable?' do
+ subject { build.erasable? }
+ it { is_expected.to eq true }
+ end
+
+ describe '#erased?' do
+ let!(:build) { create(:ci_build, :trace, :success, :artifacts) }
+ subject { build.erased? }
+
+ context 'build has not been erased' do
+ it { is_expected.to be false }
+ end
+
+ context 'build has been erased' do
+ before { build.erase }
+
+ it { is_expected.to be true }
+ end
+ end
+
+ context 'metadata and build trace are not available' do
+ let!(:build) { create(:ci_build, :success, :artifacts) }
+ before { build.remove_artifacts_metadata! }
+
+ describe '#erase' do
+ it 'should not raise error' do
+ expect { build.erase }.to_not raise_error
+ end
+ end
+ end
+ end
end
end
diff --git a/spec/models/ci/commit_spec.rb b/spec/models/ci/commit_spec.rb
index dfc0cc3be1c..4dc309a4255 100644
--- a/spec/models/ci/commit_spec.rb
+++ b/spec/models/ci/commit_spec.rb
@@ -247,6 +247,35 @@ describe Ci::Commit, models: true do
end
end
+
+ context 'custom stage with first job allowed to fail' do
+ let(:yaml) do
+ {
+ stages: ['clean', 'test'],
+ clean_job: {
+ stage: 'clean',
+ allow_failure: true,
+ script: 'BUILD',
+ },
+ test_job: {
+ stage: 'test',
+ script: 'TEST',
+ },
+ }
+ end
+
+ before do
+ stub_ci_commit_yaml_file(YAML.dump(yaml))
+ create_builds
+ end
+
+ it 'properly schedules builds' do
+ expect(commit.builds.pluck(:status)).to contain_exactly('pending')
+ commit.builds.running_or_pending.each(&:drop)
+ expect(commit.builds.pluck(:status)).to contain_exactly('pending', 'failed')
+ end
+ end
+
context 'properly creates builds when "when" is defined' do
let(:yaml) do
{
diff --git a/spec/models/ci/runner_spec.rb b/spec/models/ci/runner_spec.rb
index 232760dfeba..e891838672e 100644
--- a/spec/models/ci/runner_spec.rb
+++ b/spec/models/ci/runner_spec.rb
@@ -39,7 +39,7 @@ describe Ci::Runner, models: true do
describe :assign_to do
let!(:project) { FactoryGirl.create :empty_project }
- let!(:shared_runner) { FactoryGirl.create(:ci_shared_runner) }
+ let!(:shared_runner) { FactoryGirl.create(:ci_runner, :shared) }
before { shared_runner.assign_to(project) }
@@ -52,15 +52,15 @@ describe Ci::Runner, models: true do
subject { Ci::Runner.online }
before do
- @runner1 = FactoryGirl.create(:ci_shared_runner, contacted_at: 1.year.ago)
- @runner2 = FactoryGirl.create(:ci_shared_runner, contacted_at: 1.second.ago)
+ @runner1 = FactoryGirl.create(:ci_runner, :shared, contacted_at: 1.year.ago)
+ @runner2 = FactoryGirl.create(:ci_runner, :shared, contacted_at: 1.second.ago)
end
it { is_expected.to eq([@runner2])}
end
describe :online? do
- let(:runner) { FactoryGirl.create(:ci_shared_runner) }
+ let(:runner) { FactoryGirl.create(:ci_runner, :shared) }
subject { runner.online? }
@@ -84,7 +84,7 @@ describe Ci::Runner, models: true do
end
describe :status do
- let(:runner) { FactoryGirl.create(:ci_shared_runner, contacted_at: 1.second.ago) }
+ let(:runner) { FactoryGirl.create(:ci_runner, :shared, contacted_at: 1.second.ago) }
subject { runner.status }
@@ -115,7 +115,7 @@ describe Ci::Runner, models: true do
describe "belongs_to_one_project?" do
it "returns false if there are two projects runner assigned to" do
- runner = FactoryGirl.create(:ci_specific_runner)
+ runner = FactoryGirl.create(:ci_runner)
project = FactoryGirl.create(:empty_project)
project1 = FactoryGirl.create(:empty_project)
project.runners << runner
@@ -125,7 +125,7 @@ describe Ci::Runner, models: true do
end
it "returns true" do
- runner = FactoryGirl.create(:ci_specific_runner)
+ runner = FactoryGirl.create(:ci_runner)
project = FactoryGirl.create(:empty_project)
project.runners << runner
diff --git a/spec/models/commit_spec.rb b/spec/models/commit_spec.rb
index ecf37b40c58..253902512c3 100644
--- a/spec/models/commit_spec.rb
+++ b/spec/models/commit_spec.rb
@@ -118,4 +118,38 @@ eos
it { expect(data[:modified]).to eq([".gitmodules"]) }
it { expect(data[:removed]).to eq([]) }
end
+
+ describe '#reverts_commit?' do
+ let(:another_commit) { double(:commit, revert_description: "This reverts commit #{commit.sha}") }
+
+ it { expect(commit.reverts_commit?(another_commit)).to be_falsy }
+
+ context 'commit has no description' do
+ before { allow(commit).to receive(:description?).and_return(false) }
+
+ it { expect(commit.reverts_commit?(another_commit)).to be_falsy }
+ end
+
+ context "another_commit's description does not revert commit" do
+ before { allow(commit).to receive(:description).and_return("Foo Bar") }
+
+ it { expect(commit.reverts_commit?(another_commit)).to be_falsy }
+ end
+
+ context "another_commit's description reverts commit" do
+ before { allow(commit).to receive(:description).and_return("Foo #{another_commit.revert_description} Bar") }
+
+ it { expect(commit.reverts_commit?(another_commit)).to be_truthy }
+ end
+
+ context "another_commit's description reverts merged merge request" do
+ before do
+ revert_description = "This reverts merge request !foo123"
+ allow(another_commit).to receive(:revert_description).and_return(revert_description)
+ allow(commit).to receive(:description).and_return("Foo #{another_commit.revert_description} Bar")
+ end
+
+ it { expect(commit.reverts_commit?(another_commit)).to be_truthy }
+ end
+ end
end
diff --git a/spec/models/concerns/issuable_spec.rb b/spec/models/concerns/issuable_spec.rb
index 8f09ff03a78..600089802b2 100644
--- a/spec/models/concerns/issuable_spec.rb
+++ b/spec/models/concerns/issuable_spec.rb
@@ -69,27 +69,28 @@ describe Issue, "Issuable" do
end
describe "#to_hook_data" do
- let(:hook_data) { issue.to_hook_data(user) }
+ let(:data) { issue.to_hook_data(user) }
+ let(:project) { issue.project }
+
it "returns correct hook data" do
- expect(hook_data[:object_kind]).to eq("issue")
- expect(hook_data[:user]).to eq(user.hook_attrs)
- expect(hook_data[:repository][:name]).to eq(issue.project.name)
- expect(hook_data[:repository][:url]).to eq(issue.project.url_to_repo)
- expect(hook_data[:repository][:description]).to eq(issue.project.description)
- expect(hook_data[:repository][:homepage]).to eq(issue.project.web_url)
- expect(hook_data[:object_attributes]).to eq(issue.hook_attrs)
- expect(hook_data).to_not have_key(:assignee)
+ expect(data[:object_kind]).to eq("issue")
+ expect(data[:user]).to eq(user.hook_attrs)
+ expect(data[:object_attributes]).to eq(issue.hook_attrs)
+ expect(data).to_not have_key(:assignee)
end
context "issue is assigned" do
before { issue.update_attribute(:assignee, user) }
it "returns correct hook data" do
- expect(hook_data[:object_attributes]['assignee_id']).to eq(user.id)
- expect(hook_data[:assignee]).to eq(user.hook_attrs)
+ expect(data[:object_attributes]['assignee_id']).to eq(user.id)
+ expect(data[:assignee]).to eq(user.hook_attrs)
end
end
+
+ include_examples 'project hook data'
+ include_examples 'deprecated repository hook data'
end
describe '#card_attributes' do
diff --git a/spec/models/hooks/project_hook_spec.rb b/spec/models/hooks/project_hook_spec.rb
index 645ee0b929a..983848392b7 100644
--- a/spec/models/hooks/project_hook_spec.rb
+++ b/spec/models/hooks/project_hook_spec.rb
@@ -19,6 +19,10 @@
require 'spec_helper'
describe ProjectHook, models: true do
+ describe "Associations" do
+ it { is_expected.to belong_to :project }
+ end
+
describe '.push_hooks' do
it 'should return hooks for push events only' do
hook = create(:project_hook, push_events: true)
diff --git a/spec/models/hooks/web_hook_spec.rb b/spec/models/hooks/web_hook_spec.rb
index 7070aa4ac62..6ea99952a8f 100644
--- a/spec/models/hooks/web_hook_spec.rb
+++ b/spec/models/hooks/web_hook_spec.rb
@@ -18,20 +18,14 @@
require 'spec_helper'
-describe ProjectHook, models: true do
- describe "Associations" do
- it { is_expected.to belong_to :project }
- end
-
- describe "Mass assignment" do
- end
-
+describe WebHook, models: true do
describe "Validations" do
it { is_expected.to validate_presence_of(:url) }
- context "url format" do
+ describe 'url' do
it { is_expected.to allow_value("http://example.com").for(:url) }
- it { is_expected.to allow_value("https://excample.com").for(:url) }
+ it { is_expected.to allow_value("https://example.com").for(:url) }
+ it { is_expected.to allow_value(" https://example.com ").for(:url) }
it { is_expected.to allow_value("http://test.com/api").for(:url) }
it { is_expected.to allow_value("http://test.com/api?key=abc").for(:url) }
it { is_expected.to allow_value("http://test.com/api?key=abc&type=def").for(:url) }
@@ -39,6 +33,12 @@ describe ProjectHook, models: true do
it { is_expected.not_to allow_value("example.com").for(:url) }
it { is_expected.not_to allow_value("ftp://example.com").for(:url) }
it { is_expected.not_to allow_value("herp-and-derp").for(:url) }
+
+ it 'strips :url before saving it' do
+ hook = create(:project_hook, url: ' https://example.com ')
+
+ expect(hook.url).to eq('https://example.com')
+ end
end
end
diff --git a/spec/models/label_spec.rb b/spec/models/label_spec.rb
index 696fbf7e0aa..0614ca1e7c9 100644
--- a/spec/models/label_spec.rb
+++ b/spec/models/label_spec.rb
@@ -59,18 +59,42 @@ describe Label, models: true do
context 'using id' do
it 'returns a String reference to the object' do
expect(label.to_reference).to eq "~#{label.id}"
- expect(label.to_reference(double('project'))).to eq "~#{label.id}"
end
end
context 'using name' do
it 'returns a String reference to the object' do
- expect(label.to_reference(:name)).to eq %(~"#{label.name}")
+ expect(label.to_reference(format: :name)).to eq %(~"#{label.name}")
end
it 'uses id when name contains double quote' do
label = create(:label, name: %q{"irony"})
- expect(label.to_reference(:name)).to eq "~#{label.id}"
+ expect(label.to_reference(format: :name)).to eq "~#{label.id}"
+ end
+ end
+
+ context 'using invalid format' do
+ it 'raises error' do
+ expect { label.to_reference(format: :invalid) }
+ .to raise_error StandardError, /Unknown format/
+ end
+ end
+
+ context 'cross project reference' do
+ let(:project) { create(:project) }
+
+ context 'using name' do
+ it 'returns cross reference with label name' do
+ expect(label.to_reference(project, format: :name))
+ .to eq %Q(#{label.project.to_reference}~"#{label.name}")
+ end
+ end
+
+ context 'using id' do
+ it 'returns cross reference with label id' do
+ expect(label.to_reference(project, format: :id))
+ .to eq %Q(#{label.project.to_reference}~#{label.id})
+ end
end
end
end
diff --git a/spec/models/merge_request_spec.rb b/spec/models/merge_request_spec.rb
index c61ddf01118..59c40922abb 100644
--- a/spec/models/merge_request_spec.rb
+++ b/spec/models/merge_request_spec.rb
@@ -24,6 +24,7 @@
# merge_params :text
# merge_when_build_succeeds :boolean default(FALSE), not null
# merge_user_id :integer
+# merge_commit_sha :string
#
require 'spec_helper'
@@ -254,13 +255,86 @@ describe MergeRequest, models: true do
end
describe "#hook_attrs" do
+ let(:attrs_hash) { subject.hook_attrs.to_h }
+
+ [:source, :target].each do |key|
+ describe "#{key} key" do
+ include_examples 'project hook data', project_key: key do
+ let(:data) { attrs_hash }
+ let(:project) { subject.send("#{key}_project") }
+ end
+ end
+ end
+
it "has all the required keys" do
- attrs = subject.hook_attrs
- attrs = attrs.to_h
- expect(attrs).to include(:source)
- expect(attrs).to include(:target)
- expect(attrs).to include(:last_commit)
- expect(attrs).to include(:work_in_progress)
+ expect(attrs_hash).to include(:source)
+ expect(attrs_hash).to include(:target)
+ expect(attrs_hash).to include(:last_commit)
+ expect(attrs_hash).to include(:work_in_progress)
+ end
+ end
+
+ describe '#diverged_commits_count' do
+ let(:project) { create(:project) }
+ let(:fork_project) { create(:project, forked_from_project: project) }
+
+ context 'diverged on same repository' do
+ subject(:merge_request_with_divergence) { create(:merge_request, :diverged, source_project: project, target_project: project) }
+
+ it 'counts commits that are on target branch but not on source branch' do
+ expect(subject.diverged_commits_count).to eq(5)
+ end
+ end
+
+ context 'diverged on fork' do
+ subject(:merge_request_fork_with_divergence) { create(:merge_request, :diverged, source_project: fork_project, target_project: project) }
+
+ it 'counts commits that are on target branch but not on source branch' do
+ expect(subject.diverged_commits_count).to eq(5)
+ end
+ end
+
+ context 'rebased on fork' do
+ subject(:merge_request_rebased) { create(:merge_request, :rebased, source_project: fork_project, target_project: project) }
+
+ it 'counts commits that are on target branch but not on source branch' do
+ expect(subject.diverged_commits_count).to eq(0)
+ end
+ end
+
+ describe 'caching' do
+ before(:example) do
+ allow(Rails).to receive(:cache).and_return(ActiveSupport::Cache::MemoryStore.new)
+ end
+
+ it 'caches the output' do
+ expect(subject).to receive(:compute_diverged_commits_count).
+ once.
+ and_return(2)
+
+ subject.diverged_commits_count
+ subject.diverged_commits_count
+ end
+
+ it 'invalidates the cache when the source sha changes' do
+ expect(subject).to receive(:compute_diverged_commits_count).
+ twice.
+ and_return(2)
+
+ subject.diverged_commits_count
+ allow(subject).to receive(:source_sha).and_return('123abc')
+ subject.diverged_commits_count
+ end
+
+ it 'invalidates the cache when the target sha changes' do
+ expect(subject).to receive(:compute_diverged_commits_count).
+ twice.
+ and_return(2)
+
+ subject.diverged_commits_count
+ allow(subject).to receive(:target_sha).and_return('123abc')
+ subject.diverged_commits_count
+ end
end
end
diff --git a/spec/models/milestone_spec.rb b/spec/models/milestone_spec.rb
index 1b1380ce4e2..28f13100d15 100644
--- a/spec/models/milestone_spec.rb
+++ b/spec/models/milestone_spec.rb
@@ -60,7 +60,7 @@ describe Milestone, models: true do
end
it "should recover from dividing by zero" do
- expect(milestone.issues).to receive(:count).and_return(0)
+ expect(milestone.issues).to receive(:size).and_return(0)
expect(milestone.percent_complete).to eq(0)
end
end
@@ -114,7 +114,6 @@ describe Milestone, models: true do
end
it { expect(milestone.closed_items_count).to eq(1) }
- it { expect(milestone.open_items_count).to eq(2) }
it { expect(milestone.total_items_count).to eq(3) }
it { expect(milestone.is_empty?).to be_falsey }
end
diff --git a/spec/models/note_spec.rb b/spec/models/note_spec.rb
index 9182b42661d..33085dac4ea 100644
--- a/spec/models/note_spec.rb
+++ b/spec/models/note_spec.rb
@@ -24,8 +24,10 @@ require 'spec_helper'
describe Note, models: true do
describe 'associations' do
it { is_expected.to belong_to(:project) }
- it { is_expected.to belong_to(:noteable) }
+ it { is_expected.to belong_to(:noteable).touch(true) }
it { is_expected.to belong_to(:author).class_name('User') }
+
+ it { is_expected.to have_many(:todos).dependent(:destroy) }
end
describe 'validation' do
@@ -203,11 +205,19 @@ describe Note, models: true do
end
describe "set_award!" do
- let(:issue) { create :issue }
+ let(:merge_request) { create :merge_request }
it "converts aliases to actual name" do
- note = create :note, note: ":+1:", noteable: issue
+ note = create(:note, note: ":+1:", noteable: merge_request)
expect(note.reload.note).to eq("thumbsup")
end
+
+ it "is not an award emoji when comment is on a diff" do
+ note = create(:note, note: ":blowfish:", noteable: merge_request, line_code: "11d5d2e667e9da4f7f610f81d86c974b146b13bd_0_2")
+ note = note.reload
+
+ expect(note.note).to eq(":blowfish:")
+ expect(note.is_award?).to be_falsy
+ end
end
end
diff --git a/spec/models/project_spec.rb b/spec/models/project_spec.rb
index a3de23369e1..f9842d23afa 100644
--- a/spec/models/project_spec.rb
+++ b/spec/models/project_spec.rb
@@ -102,7 +102,7 @@ describe Project, models: true do
expect(project2.errors[:limit_reached].first).to match(/Your project limit is 0/)
end
end
-
+
describe 'project token' do
it 'should set an random token if none provided' do
project = FactoryGirl.create :empty_project, runners_token: ''
@@ -519,35 +519,35 @@ describe Project, models: true do
describe :any_runners do
let(:project) { create(:empty_project, shared_runners_enabled: shared_runners_enabled) }
- let(:specific_runner) { create(:ci_specific_runner) }
- let(:shared_runner) { create(:ci_shared_runner) }
+ let(:specific_runner) { create(:ci_runner) }
+ let(:shared_runner) { create(:ci_runner, :shared) }
context 'for shared runners disabled' do
let(:shared_runners_enabled) { false }
-
+
it 'there are no runners available' do
expect(project.any_runners?).to be_falsey
end
-
+
it 'there is a specific runner' do
project.runners << specific_runner
expect(project.any_runners?).to be_truthy
end
-
+
it 'there is a shared runner, but they are prohibited to use' do
shared_runner
expect(project.any_runners?).to be_falsey
end
-
+
it 'checks the presence of specific runner' do
project.runners << specific_runner
expect(project.any_runners? { |runner| runner == specific_runner }).to be_truthy
end
end
-
+
context 'for shared runners enabled' do
let(:shared_runners_enabled) { true }
-
+
it 'there is a shared runner' do
shared_runner
expect(project.any_runners?).to be_truthy
@@ -583,4 +583,67 @@ describe Project, models: true do
end
end
+
+ describe '#rename_repo' do
+ let(:project) { create(:project) }
+ let(:gitlab_shell) { Gitlab::Shell.new }
+
+ before do
+ # Project#gitlab_shell returns a new instance of Gitlab::Shell on every
+ # call. This makes testing a bit easier.
+ allow(project).to receive(:gitlab_shell).and_return(gitlab_shell)
+ end
+
+ it 'renames a repository' do
+ allow(project).to receive(:previous_changes).and_return('path' => ['foo'])
+
+ ns = project.namespace_dir
+
+ expect(gitlab_shell).to receive(:mv_repository).
+ ordered.
+ with("#{ns}/foo", "#{ns}/#{project.path}").
+ and_return(true)
+
+ expect(gitlab_shell).to receive(:mv_repository).
+ ordered.
+ with("#{ns}/foo.wiki", "#{ns}/#{project.path}.wiki").
+ and_return(true)
+
+ expect_any_instance_of(SystemHooksService).
+ to receive(:execute_hooks_for).
+ with(project, :rename)
+
+ expect_any_instance_of(Gitlab::UploadsTransfer).
+ to receive(:rename_project).
+ with('foo', project.path, ns)
+
+ expect(project).to receive(:expire_caches_before_rename)
+
+ project.rename_repo
+ end
+ end
+
+ describe '#expire_caches_before_rename' do
+ let(:project) { create(:project) }
+ let(:repo) { double(:repo, exists?: true) }
+ let(:wiki) { double(:wiki, exists?: true) }
+
+ it 'expires the caches of the repository and wiki' do
+ allow(Repository).to receive(:new).
+ with('foo', project).
+ and_return(repo)
+
+ allow(Repository).to receive(:new).
+ with('foo.wiki', project).
+ and_return(wiki)
+
+ expect(repo).to receive(:expire_cache)
+ expect(repo).to receive(:expire_emptiness_caches)
+
+ expect(wiki).to receive(:expire_cache)
+ expect(wiki).to receive(:expire_emptiness_caches)
+
+ project.expire_caches_before_rename('foo')
+ end
+ end
end
diff --git a/spec/models/project_team_spec.rb b/spec/models/project_team_spec.rb
index 5cd5ae327bf..7b63da005f0 100644
--- a/spec/models/project_team_spec.rb
+++ b/spec/models/project_team_spec.rb
@@ -68,14 +68,24 @@ describe ProjectTeam, models: true do
end
describe "#human_max_access" do
- it "return master role" do
- user = create :user
- group = create :group
- group.add_users([user.id], GroupMember::MASTER)
- project = create(:project, namespace: group)
- project.team << [user, :guest]
-
- expect(project.team.human_max_access(user.id)).to eq("Master")
+ it 'returns Master role' do
+ user = create(:user)
+ group = create(:group)
+ group.add_master(user)
+
+ project = build_stubbed(:empty_project, namespace: group)
+
+ expect(project.team.human_max_access(user.id)).to eq 'Master'
+ end
+
+ it 'returns Owner role' do
+ user = create(:user)
+ group = create(:group)
+ group.add_owner(user)
+
+ project = build_stubbed(:empty_project, namespace: group)
+
+ expect(project.team.human_max_access(user.id)).to eq 'Owner'
end
end
end
diff --git a/spec/models/repository_spec.rb b/spec/models/repository_spec.rb
index e1ee43e64db..1c7d66398cb 100644
--- a/spec/models/repository_spec.rb
+++ b/spec/models/repository_spec.rb
@@ -5,6 +5,15 @@ describe Repository, models: true do
let(:repository) { create(:project).repository }
let(:user) { create(:user) }
+ let(:commit_options) do
+ author = repository.user_to_committer(user)
+ { message: 'Test message', committer: author, author: author }
+ end
+ let(:merge_commit) do
+ source_sha = repository.find_branch('feature').target
+ merge_commit_id = repository.merge(user, source_sha, 'master', commit_options)
+ repository.commit(merge_commit_id)
+ end
describe :branch_names_contains do
subject { repository.branch_names_contains(sample_commit.id) }
@@ -200,13 +209,22 @@ describe Repository, models: true do
describe :commit_with_hooks do
context 'when pre hooks were successful' do
- it 'should run without errors' do
- expect_any_instance_of(GitHooksService).to receive(:execute).and_return(true)
+ before do
+ expect_any_instance_of(GitHooksService).to receive(:execute).
+ and_return(true)
+ end
+ it 'should run without errors' do
expect do
repository.commit_with_hooks(user, 'feature') { sample_commit.id }
end.not_to raise_error
end
+
+ it 'should ensure the autocrlf Git option is set to :input' do
+ expect(repository).to receive(:update_autocrlf_option)
+
+ repository.commit_with_hooks(user, 'feature') { sample_commit.id }
+ end
end
context 'when pre hooks failed' do
@@ -220,6 +238,25 @@ describe Repository, models: true do
end
end
+ describe '#exists?' do
+ it 'returns true when a repository exists' do
+ expect(repository.exists?).to eq(true)
+ end
+
+ it 'returns false when a repository does not exist' do
+ expect(repository.raw_repository).to receive(:rugged).
+ and_raise(Gitlab::Git::Repository::NoRepository)
+
+ expect(repository.exists?).to eq(false)
+ end
+
+ it 'returns false when there is no namespace' do
+ allow(repository).to receive(:path_with_namespace).and_return(nil)
+
+ expect(repository.exists?).to eq(false)
+ end
+ end
+
describe '#has_visible_content?' do
subject { repository.has_visible_content? }
@@ -249,6 +286,33 @@ describe Repository, models: true do
end
end
+ describe '#update_autocrlf_option' do
+ describe 'when autocrlf is not already set to :input' do
+ before do
+ repository.raw_repository.autocrlf = true
+ end
+
+ it 'sets autocrlf to :input' do
+ repository.update_autocrlf_option
+
+ expect(repository.raw_repository.autocrlf).to eq(:input)
+ end
+ end
+
+ describe 'when autocrlf is already set to :input' do
+ before do
+ repository.raw_repository.autocrlf = :input
+ end
+
+ it 'does nothing' do
+ expect(repository.raw_repository).to_not receive(:autocrlf=).
+ with(:input)
+
+ repository.update_autocrlf_option
+ end
+ end
+ end
+
describe '#empty?' do
let(:empty_repository) { create(:project_empty_repo).repository }
@@ -297,6 +361,20 @@ describe Repository, models: true do
repository.expire_cache('master')
end
+
+ it 'expires the emptiness caches for an empty repository' do
+ expect(repository).to receive(:empty?).and_return(true)
+ expect(repository).to receive(:expire_emptiness_caches)
+
+ repository.expire_cache
+ end
+
+ it 'does not expire the emptiness caches for a non-empty repository' do
+ expect(repository).to receive(:empty?).and_return(false)
+ expect(repository).to_not receive(:expire_emptiness_caches)
+
+ repository.expire_cache
+ end
end
describe '#expire_root_ref_cache' do
@@ -355,9 +433,166 @@ describe Repository, models: true do
end
end
+ describe '#expire_emptiness_caches' do
+ let(:cache) { repository.send(:cache) }
+
+ it 'expires the caches' do
+ expect(cache).to receive(:expire).with(:empty?)
+ expect(repository).to receive(:expire_has_visible_content_cache)
+
+ repository.expire_emptiness_caches
+ end
+ end
+
describe :skip_merged_commit do
subject { repository.commits(Gitlab::Git::BRANCH_REF_PREFIX + "'test'", nil, 100, 0, true).map{ |k| k.id } }
it { is_expected.not_to include('e56497bb5f03a90a51293fc6d516788730953899') }
end
+
+ describe '#merge' do
+ it 'should merge the code and return the commit id' do
+ expect(merge_commit).to be_present
+ expect(repository.blob_at(merge_commit.id, 'files/ruby/feature.rb')).to be_present
+ end
+ end
+
+ describe '#revert' do
+ let(:new_image_commit) { repository.commit('33f3729a45c02fc67d00adb1b8bca394b0e761d9') }
+ let(:update_image_commit) { repository.commit('2f63565e7aac07bcdadb654e253078b727143ec4') }
+
+ context 'when there is a conflict' do
+ it 'should abort the operation' do
+ expect(repository.revert(user, new_image_commit, 'master')).to eq(false)
+ end
+ end
+
+ context 'when commit was already reverted' do
+ it 'should abort the operation' do
+ repository.revert(user, update_image_commit, 'master')
+
+ expect(repository.revert(user, update_image_commit, 'master')).to eq(false)
+ end
+ end
+
+ context 'when commit can be reverted' do
+ it 'should revert the changes' do
+ expect(repository.revert(user, update_image_commit, 'master')).to be_truthy
+ end
+ end
+
+ context 'reverting a merge commit' do
+ it 'should revert the changes' do
+ merge_commit
+ expect(repository.blob_at_branch('master', 'files/ruby/feature.rb')).to be_present
+
+ repository.revert(user, merge_commit, 'master')
+ expect(repository.blob_at_branch('master', 'files/ruby/feature.rb')).not_to be_present
+ end
+ end
+ end
+
+ describe '#before_delete' do
+ describe 'when a repository does not exist' do
+ before do
+ allow(repository).to receive(:exists?).and_return(false)
+ end
+
+ it 'does not flush caches that depend on repository data' do
+ expect(repository).to_not receive(:expire_cache)
+
+ repository.before_delete
+ end
+
+ it 'flushes the root ref cache' do
+ expect(repository).to receive(:expire_root_ref_cache)
+
+ repository.before_delete
+ end
+
+ it 'flushes the emptiness caches' do
+ expect(repository).to receive(:expire_emptiness_caches)
+
+ repository.before_delete
+ end
+ end
+
+ describe 'when a repository exists' do
+ before do
+ allow(repository).to receive(:exists?).and_return(true)
+ end
+
+ it 'flushes the caches that depend on repository data' do
+ expect(repository).to receive(:expire_cache)
+
+ repository.before_delete
+ end
+
+ it 'flushes the root ref cache' do
+ expect(repository).to receive(:expire_root_ref_cache)
+
+ repository.before_delete
+ end
+
+ it 'flushes the emptiness caches' do
+ expect(repository).to receive(:expire_emptiness_caches)
+
+ repository.before_delete
+ end
+ end
+ end
+
+ describe '#before_change_head' do
+ it 'flushes the branch cache' do
+ expect(repository).to receive(:expire_branch_cache)
+
+ repository.before_change_head
+ end
+
+ it 'flushes the root ref cache' do
+ expect(repository).to receive(:expire_root_ref_cache)
+
+ repository.before_change_head
+ end
+ end
+
+ describe '#before_create_tag' do
+ it 'flushes the cache' do
+ expect(repository).to receive(:expire_cache)
+
+ repository.before_create_tag
+ end
+ end
+
+ describe '#after_import' do
+ it 'flushes the emptiness cachess' do
+ expect(repository).to receive(:expire_emptiness_caches)
+
+ repository.after_import
+ end
+ end
+
+ describe '#after_push_commit' do
+ it 'flushes the cache' do
+ expect(repository).to receive(:expire_cache).with('master')
+
+ repository.after_push_commit('master')
+ end
+ end
+
+ describe '#after_create_branch' do
+ it 'flushes the visible content cache' do
+ expect(repository).to receive(:expire_has_visible_content_cache)
+
+ repository.after_create_branch
+ end
+ end
+
+ describe '#after_remove_branch' do
+ it 'flushes the visible content cache' do
+ expect(repository).to receive(:expire_has_visible_content_cache)
+
+ repository.after_remove_branch
+ end
+ end
end
diff --git a/spec/models/todo_spec.rb b/spec/models/todo_spec.rb
new file mode 100644
index 00000000000..fe9ea7e7d1e
--- /dev/null
+++ b/spec/models/todo_spec.rb
@@ -0,0 +1,69 @@
+# == Schema Information
+#
+# Table name: todos
+#
+# id :integer not null, primary key
+# user_id :integer not null
+# project_id :integer not null
+# target_id :integer not null
+# target_type :string not null
+# author_id :integer
+# note_id :integer
+# action :integer not null
+# state :string not null
+# created_at :datetime
+# updated_at :datetime
+#
+
+require 'spec_helper'
+
+describe Todo, models: true do
+ describe 'relationships' do
+ it { is_expected.to belong_to(:author).class_name("User") }
+ it { is_expected.to belong_to(:note) }
+ it { is_expected.to belong_to(:project) }
+ it { is_expected.to belong_to(:target).touch(true) }
+ it { is_expected.to belong_to(:user) }
+ end
+
+ describe 'respond to' do
+ it { is_expected.to respond_to(:author_name) }
+ it { is_expected.to respond_to(:author_email) }
+ end
+
+ describe 'validations' do
+ it { is_expected.to validate_presence_of(:action) }
+ it { is_expected.to validate_presence_of(:target) }
+ it { is_expected.to validate_presence_of(:user) }
+ end
+
+ describe '#body' do
+ before do
+ subject.target = build(:issue, title: 'Bugfix')
+ end
+
+ it 'returns target title when note is blank' do
+ subject.note = nil
+
+ expect(subject.body).to eq 'Bugfix'
+ end
+
+ it 'returns note when note is present' do
+ subject.note = build(:note, note: 'quick fix')
+
+ expect(subject.body).to eq 'quick fix'
+ end
+ end
+
+ describe '#done!' do
+ it 'changes state to done' do
+ todo = create(:todo, state: :pending)
+ expect { todo.done! }.to change(todo, :state).from('pending').to('done')
+ end
+
+ it 'does not raise error when is already done' do
+ todo = create(:todo, state: :done)
+ expect { todo.done! }.not_to raise_error
+ end
+ end
+end
diff --git a/spec/models/user_spec.rb b/spec/models/user_spec.rb
index 47ce409fe4b..412101ac9f9 100644
--- a/spec/models/user_spec.rb
+++ b/spec/models/user_spec.rb
@@ -92,6 +92,7 @@ describe User, models: true do
it { is_expected.to have_many(:identities).dependent(:destroy) }
it { is_expected.to have_one(:abuse_report) }
it { is_expected.to have_many(:spam_logs).dependent(:destroy) }
+ it { is_expected.to have_many(:todos).dependent(:destroy) }
end
describe 'validations' do
@@ -173,6 +174,32 @@ describe User, models: true do
end
end
end
+
+ describe 'avatar' do
+ it 'only validates when avatar is present and changed' do
+ user = build(:user, :with_avatar)
+
+ user.avatar_crop_x = nil
+ user.avatar_crop_y = nil
+ user.avatar_crop_size = nil
+
+ expect(user).not_to be_valid
+ expect(user.errors.keys).
+ to match_array %i(avatar_crop_x avatar_crop_y avatar_crop_size)
+ end
+
+ it 'does not validate when avatar has not changed' do
+ user = create(:user, :with_avatar)
+
+ expect { user.avatar_crop_x = nil }.not_to change(user, :valid?)
+ end
+
+ it 'does not validate when avatar is not present' do
+ user = create(:user)
+
+ expect { user.avatar_crop_y = nil }.not_to change(user, :valid?)
+ end
+ end
end
describe "Respond to" do
@@ -255,6 +282,7 @@ describe User, models: true do
expect(user).to be_two_factor_enabled
expect(user.encrypted_otp_secret).not_to be_nil
expect(user.otp_backup_codes).not_to be_nil
+ expect(user.otp_grace_period_started_at).not_to be_nil
user.disable_two_factor!
@@ -263,6 +291,7 @@ describe User, models: true do
expect(user.encrypted_otp_secret_iv).to be_nil
expect(user.encrypted_otp_secret_salt).to be_nil
expect(user.otp_backup_codes).to be_nil
+ expect(user.otp_grace_period_started_at).to be_nil
end
end
diff --git a/spec/requests/api/branches_spec.rb b/spec/requests/api/branches_spec.rb
index 36461e84c3a..55582aa53d2 100644
--- a/spec/requests/api/branches_spec.rb
+++ b/spec/requests/api/branches_spec.rb
@@ -7,8 +7,8 @@ describe API::API, api: true do
let(:user) { create(:user) }
let(:user2) { create(:user) }
let!(:project) { create(:project, creator_id: user.id) }
- let!(:master) { create(:project_member, user: user, project: project, access_level: ProjectMember::MASTER) }
- let!(:guest) { create(:project_member, user: user2, project: project, access_level: ProjectMember::GUEST) }
+ let!(:master) { create(:project_member, :master, user: user, project: project) }
+ let!(:guest) { create(:project_member, :guest, user: user2, project: project) }
let!(:branch_name) { 'feature' }
let!(:branch_sha) { '0b4bc9a49b562e85de7cc9e834518ea6828729b9' }
diff --git a/spec/requests/api/builds_spec.rb b/spec/requests/api/builds_spec.rb
index 6c07802db8b..967c34800d0 100644
--- a/spec/requests/api/builds_spec.rb
+++ b/spec/requests/api/builds_spec.rb
@@ -4,148 +4,190 @@ describe API::API, api: true do
include ApiHelpers
let(:user) { create(:user) }
+ let(:api_user) { user }
let(:user2) { create(:user) }
let!(:project) { create(:project, creator_id: user.id) }
- let!(:developer) { create(:project_member, user: user, project: project, access_level: ProjectMember::DEVELOPER) }
- let!(:reporter) { create(:project_member, user: user2, project: project, access_level: ProjectMember::REPORTER) }
+ let!(:developer) { create(:project_member, :developer, user: user, project: project) }
+ let!(:reporter) { create(:project_member, :reporter, user: user2, project: project) }
let(:commit) { create(:ci_commit, project: project)}
let(:build) { create(:ci_build, commit: commit) }
- let(:build_with_trace) { create(:ci_build_with_trace, commit: commit) }
- let(:build_canceled) { create(:ci_build, :canceled, commit: commit) }
describe 'GET /projects/:id/builds ' do
+ let(:query) { '' }
+
+ before { get api("/projects/#{project.id}/builds?#{query}", api_user) }
+
context 'authorized user' do
it 'should return project builds' do
- get api("/projects/#{project.id}/builds", user)
-
expect(response.status).to eq(200)
expect(json_response).to be_an Array
end
- it 'should filter project with one scope element' do
- get api("/projects/#{project.id}/builds?scope=pending", user)
+ context 'filter project with one scope element' do
+ let(:query) { 'scope=pending' }
- expect(response.status).to eq(200)
- expect(json_response).to be_an Array
+ it do
+ expect(response.status).to eq(200)
+ expect(json_response).to be_an Array
+ end
end
- it 'should filter project with array of scope elements' do
- get api("/projects/#{project.id}/builds?scope[0]=pending&scope[1]=running", user)
+ context 'filter project with array of scope elements' do
+ let(:query) { 'scope[0]=pending&scope[1]=running' }
- expect(response.status).to eq(200)
- expect(json_response).to be_an Array
+ it do
+ expect(response.status).to eq(200)
+ expect(json_response).to be_an Array
+ end
end
- it 'should respond 400 when scope contains invalid state' do
- get api("/projects/#{project.id}/builds?scope[0]=pending&scope[1]=unknown_status", user)
+ context 'respond 400 when scope contains invalid state' do
+ let(:query) { 'scope[0]=pending&scope[1]=unknown_status' }
- expect(response.status).to eq(400)
+ it { expect(response.status).to eq(400) }
end
end
context 'unauthorized user' do
- it 'should not return project builds' do
- get api("/projects/#{project.id}/builds")
+ let(:api_user) { nil }
+ it 'should not return project builds' do
expect(response.status).to eq(401)
end
end
end
describe 'GET /projects/:id/repository/commits/:sha/builds' do
+ before do
+ project.ensure_ci_commit(commit.sha)
+ get api("/projects/#{project.id}/repository/commits/#{commit.sha}/builds", api_user)
+ end
+
context 'authorized user' do
it 'should return project builds for specific commit' do
- project.ensure_ci_commit(commit.sha)
- get api("/projects/#{project.id}/repository/commits/#{commit.sha}/builds", user)
-
expect(response.status).to eq(200)
expect(json_response).to be_an Array
end
end
context 'unauthorized user' do
- it 'should not return project builds' do
- project.ensure_ci_commit(commit.sha)
- get api("/projects/#{project.id}/repository/commits/#{commit.sha}/builds")
+ let(:api_user) { nil }
+ it 'should not return project builds' do
expect(response.status).to eq(401)
end
end
end
describe 'GET /projects/:id/builds/:build_id' do
+ before { get api("/projects/#{project.id}/builds/#{build.id}", api_user) }
+
context 'authorized user' do
it 'should return specific build data' do
- get api("/projects/#{project.id}/builds/#{build.id}", user)
-
expect(response.status).to eq(200)
expect(json_response['name']).to eq('test')
end
end
context 'unauthorized user' do
- it 'should not return specific build data' do
- get api("/projects/#{project.id}/builds/#{build.id}")
+ let(:api_user) { nil }
+ it 'should not return specific build data' do
expect(response.status).to eq(401)
end
end
end
+ describe 'GET /projects/:id/builds/:build_id/artifacts' do
+ before { get api("/projects/#{project.id}/builds/#{build.id}/artifacts", api_user) }
+
+ context 'build with artifacts' do
+ let(:build) { create(:ci_build, :artifacts, commit: commit) }
+
+ context 'authorized user' do
+ let(:download_headers) do
+ { 'Content-Transfer-Encoding'=>'binary',
+ 'Content-Disposition'=>'attachment; filename=ci_build_artifacts.zip' }
+ end
+
+ it 'should return specific build artifacts' do
+ expect(response.status).to eq(200)
+ expect(response.headers).to include(download_headers)
+ end
+ end
+
+ context 'unauthorized user' do
+ let(:api_user) { nil }
+
+ it 'should not return specific build artifacts' do
+ expect(response.status).to eq(401)
+ end
+ end
+ end
+
+ it 'should not return build artifacts if not uploaded' do
+ expect(response.status).to eq(404)
+ end
+ end
+
describe 'GET /projects/:id/builds/:build_id/trace' do
+ let(:build) { create(:ci_build, :trace, commit: commit) }
+
+ before { get api("/projects/#{project.id}/builds/#{build.id}/trace", api_user) }
+
context 'authorized user' do
it 'should return specific build trace' do
- get api("/projects/#{project.id}/builds/#{build_with_trace.id}/trace", user)
-
expect(response.status).to eq(200)
- expect(response.body).to eq(build_with_trace.trace)
+ expect(response.body).to eq(build.trace)
end
end
context 'unauthorized user' do
- it 'should not return specific build trace' do
- get api("/projects/#{project.id}/builds/#{build_with_trace.id}/trace")
+ let(:api_user) { nil }
+ it 'should not return specific build trace' do
expect(response.status).to eq(401)
end
end
end
describe 'POST /projects/:id/builds/:build_id/cancel' do
+ before { post api("/projects/#{project.id}/builds/#{build.id}/cancel", api_user) }
+
context 'authorized user' do
context 'user with :update_build persmission' do
it 'should cancel running or pending build' do
- post api("/projects/#{project.id}/builds/#{build.id}/cancel", user)
-
expect(response.status).to eq(201)
expect(project.builds.first.status).to eq('canceled')
end
end
context 'user without :update_build permission' do
- it 'should not cancel build' do
- post api("/projects/#{project.id}/builds/#{build.id}/cancel", user2)
+ let(:api_user) { user2 }
+ it 'should not cancel build' do
expect(response.status).to eq(403)
end
end
end
context 'unauthorized user' do
- it 'should not cancel build' do
- post api("/projects/#{project.id}/builds/#{build.id}/cancel")
+ let(:api_user) { nil }
+ it 'should not cancel build' do
expect(response.status).to eq(401)
end
end
end
describe 'POST /projects/:id/builds/:build_id/retry' do
+ let(:build) { create(:ci_build, :canceled, commit: commit) }
+
+ before { post api("/projects/#{project.id}/builds/#{build.id}/retry", api_user) }
+
context 'authorized user' do
- context 'user with :update_build persmission' do
+ context 'user with :update_build permission' do
it 'should retry non-running build' do
- post api("/projects/#{project.id}/builds/#{build_canceled.id}/retry", user)
-
expect(response.status).to eq(201)
expect(project.builds.first.status).to eq('canceled')
expect(json_response['status']).to eq('pending')
@@ -153,20 +195,50 @@ describe API::API, api: true do
end
context 'user without :update_build permission' do
- it 'should not retry build' do
- post api("/projects/#{project.id}/builds/#{build_canceled.id}/retry", user2)
+ let(:api_user) { user2 }
+ it 'should not retry build' do
expect(response.status).to eq(403)
end
end
end
context 'unauthorized user' do
- it 'should not retry build' do
- post api("/projects/#{project.id}/builds/#{build_canceled.id}/retry")
+ let(:api_user) { nil }
+ it 'should not retry build' do
expect(response.status).to eq(401)
end
end
end
+
+ describe 'POST /projects/:id/builds/:build_id/erase' do
+ before do
+ post api("/projects/#{project.id}/builds/#{build.id}/erase", user)
+ end
+
+ context 'build is erasable' do
+ let(:build) { create(:ci_build, :trace, :artifacts, :success, project: project, commit: commit) }
+
+ it 'should erase build content' do
+ expect(response.status).to eq 201
+ expect(build.trace).to be_empty
+ expect(build.artifacts_file.exists?).to be_falsy
+ expect(build.artifacts_metadata.exists?).to be_falsy
+ end
+
+ it 'should update build' do
+ expect(build.reload.erased_at).to be_truthy
+ expect(build.reload.erased_by).to eq user
+ end
+ end
+
+ context 'build is not erasable' do
+ let(:build) { create(:ci_build, :trace, project: project, commit: commit) }
+
+ it 'should respond with forbidden' do
+ expect(response.status).to eq 403
+ end
+ end
+ end
end
diff --git a/spec/requests/api/commit_status_spec.rb b/spec/requests/api/commit_status_spec.rb
index 89b554622ef..429a24109fd 100644
--- a/spec/requests/api/commit_status_spec.rb
+++ b/spec/requests/api/commit_status_spec.rb
@@ -2,88 +2,125 @@ require 'spec_helper'
describe API::CommitStatus, api: true do
include ApiHelpers
+
let!(:project) { create(:project) }
let(:commit) { project.repository.commit }
- let!(:ci_commit) { project.ensure_ci_commit(commit.id) }
let(:commit_status) { create(:commit_status, commit: ci_commit) }
- let(:guest) { create_user(ProjectMember::GUEST) }
- let(:reporter) { create_user(ProjectMember::REPORTER) }
- let(:developer) { create_user(ProjectMember::DEVELOPER) }
+ let(:guest) { create_user(:guest) }
+ let(:reporter) { create_user(:reporter) }
+ let(:developer) { create_user(:developer) }
+ let(:sha) { commit.id }
+
describe "GET /projects/:id/repository/commits/:sha/statuses" do
- it_behaves_like 'a paginated resources' do
- let(:request) { get api("/projects/#{project.id}/repository/commits/#{commit.id}/statuses", reporter) }
- end
+ let(:get_url) { "/projects/#{project.id}/repository/commits/#{sha}/statuses" }
- context "reporter user" do
- let(:statuses_id) { json_response.map { |status| status['id'] } }
+ context 'ci commit exists' do
+ let!(:ci_commit) { project.ensure_ci_commit(commit.id) }
- before do
- @status1 = create(:commit_status, commit: ci_commit, status: 'running')
- @status2 = create(:commit_status, commit: ci_commit, name: 'coverage', status: 'pending')
- @status3 = create(:commit_status, commit: ci_commit, name: 'coverage', ref: 'develop', status: 'running', allow_failure: true)
- @status4 = create(:commit_status, commit: ci_commit, name: 'coverage', status: 'success')
- @status5 = create(:commit_status, commit: ci_commit, ref: 'develop', status: 'success')
- @status6 = create(:commit_status, commit: ci_commit, status: 'success')
+ it_behaves_like 'a paginated resources' do
+ let(:request) { get api(get_url, reporter) }
end
- it "should return latest commit statuses" do
- get api("/projects/#{project.id}/repository/commits/#{commit.id}/statuses", reporter)
- expect(response.status).to eq(200)
+ context "reporter user" do
+ let(:statuses_id) { json_response.map { |status| status['id'] } }
- expect(json_response).to be_an Array
- expect(statuses_id).to contain_exactly(@status3.id, @status4.id, @status5.id, @status6.id)
- json_response.sort_by!{ |status| status['id'] }
- expect(json_response.map{ |status| status['allow_failure'] }).to eq([true, false, false, false])
- end
+ def create_status(opts = {})
+ create(:commit_status, { commit: ci_commit }.merge(opts))
+ end
- it "should return all commit statuses" do
- get api("/projects/#{project.id}/repository/commits/#{commit.id}/statuses?all=1", reporter)
- expect(response.status).to eq(200)
+ let!(:status1) { create_status(status: 'running') }
+ let!(:status2) { create_status(name: 'coverage', status: 'pending') }
+ let!(:status3) { create_status(ref: 'develop', status: 'running', allow_failure: true) }
+ let!(:status4) { create_status(name: 'coverage', status: 'success') }
+ let!(:status5) { create_status(name: 'coverage', ref: 'develop', status: 'success') }
+ let!(:status6) { create_status(status: 'success') }
- expect(json_response).to be_an Array
- expect(statuses_id).to contain_exactly(@status1.id, @status2.id, @status3.id, @status4.id, @status5.id, @status6.id)
- end
+ context 'latest commit statuses' do
+ before { get api(get_url, reporter) }
- it "should return latest commit statuses for specific ref" do
- get api("/projects/#{project.id}/repository/commits/#{commit.id}/statuses?ref=develop", reporter)
- expect(response.status).to eq(200)
+ it 'returns latest commit statuses' do
+ expect(response.status).to eq(200)
- expect(json_response).to be_an Array
- expect(statuses_id).to contain_exactly(@status3.id, @status5.id)
+ expect(json_response).to be_an Array
+ expect(statuses_id).to contain_exactly(status3.id, status4.id, status5.id, status6.id)
+ json_response.sort_by!{ |status| status['id'] }
+ expect(json_response.map{ |status| status['allow_failure'] }).to eq([true, false, false, false])
+ end
+ end
+
+ context 'all commit statuses' do
+ before { get api(get_url, reporter), all: 1 }
+
+ it 'returns all commit statuses' do
+ expect(response.status).to eq(200)
+
+ expect(json_response).to be_an Array
+ expect(statuses_id).to contain_exactly(status1.id, status2.id,
+ status3.id, status4.id,
+ status5.id, status6.id)
+ end
+ end
+
+ context 'latest commit statuses for specific ref' do
+ before { get api(get_url, reporter), ref: 'develop' }
+
+ it 'returns latest commit statuses for specific ref' do
+ expect(response.status).to eq(200)
+
+ expect(json_response).to be_an Array
+ expect(statuses_id).to contain_exactly(status3.id, status5.id)
+ end
+ end
+
+ context 'latest commit statues for specific name' do
+ before { get api(get_url, reporter), name: 'coverage' }
+
+ it 'return latest commit statuses for specific name' do
+ expect(response.status).to eq(200)
+
+ expect(json_response).to be_an Array
+ expect(statuses_id).to contain_exactly(status4.id, status5.id)
+ end
+ end
end
+ end
- it "should return latest commit statuses for specific name" do
- get api("/projects/#{project.id}/repository/commits/#{commit.id}/statuses?name=coverage", reporter)
- expect(response.status).to eq(200)
+ context 'ci commit does not exist' do
+ before { get api(get_url, reporter) }
+ it 'returns empty array' do
+ expect(response.status).to eq 200
expect(json_response).to be_an Array
- expect(statuses_id).to contain_exactly(@status3.id, @status4.id)
+ expect(json_response).to be_empty
end
end
context "guest user" do
+ before { get api(get_url, guest) }
+
it "should not return project commits" do
- get api("/projects/#{project.id}/repository/commits/#{commit.id}/statuses", guest)
expect(response.status).to eq(403)
end
end
context "unauthorized user" do
+ before { get api(get_url) }
+
it "should not return project commits" do
- get api("/projects/#{project.id}/repository/commits/#{commit.id}/statuses")
expect(response.status).to eq(401)
end
end
end
describe 'POST /projects/:id/statuses/:sha' do
- let(:post_url) { "/projects/#{project.id}/statuses/#{commit.id}" }
+ let(:post_url) { "/projects/#{project.id}/statuses/#{sha}" }
context 'developer user' do
- context 'should create commit status' do
- it 'with only required parameters' do
- post api(post_url, developer), state: 'success'
+ context 'only required parameters' do
+ before { post api(post_url, developer), state: 'success' }
+
+ it 'creates commit status' do
expect(response.status).to eq(201)
expect(json_response['sha']).to eq(commit.id)
expect(json_response['status']).to eq('success')
@@ -92,9 +129,17 @@ describe API::CommitStatus, api: true do
expect(json_response['target_url']).to be_nil
expect(json_response['description']).to be_nil
end
+ end
- it 'with all optional parameters' do
- post api(post_url, developer), state: 'success', context: 'coverage', ref: 'develop', target_url: 'url', description: 'test'
+ context 'with all optional parameters' do
+ before do
+ optional_params = { state: 'success', context: 'coverage',
+ ref: 'develop', target_url: 'url', description: 'test' }
+
+ post api(post_url, developer), optional_params
+ end
+
+ it 'creates commit status' do
expect(response.status).to eq(201)
expect(json_response['sha']).to eq(commit.id)
expect(json_response['status']).to eq('success')
@@ -105,49 +150,60 @@ describe API::CommitStatus, api: true do
end
end
- context 'should not create commit status' do
- it 'with invalid state' do
- post api(post_url, developer), state: 'invalid'
+ context 'invalid status' do
+ before { post api(post_url, developer), state: 'invalid' }
+
+ it 'does not create commit status' do
expect(response.status).to eq(400)
end
+ end
- it 'without state' do
- post api(post_url, developer)
+ context 'request without state' do
+ before { post api(post_url, developer) }
+
+ it 'does not create commit status' do
expect(response.status).to eq(400)
end
+ end
- it 'invalid commit' do
- post api("/projects/#{project.id}/statuses/invalid_sha", developer), state: 'running'
+ context 'invalid commit' do
+ let(:sha) { 'invalid_sha' }
+ before { post api(post_url, developer), state: 'running' }
+
+ it 'returns not found error' do
expect(response.status).to eq(404)
end
end
end
context 'reporter user' do
+ before { post api(post_url, reporter) }
+
it 'should not create commit status' do
- post api(post_url, reporter)
expect(response.status).to eq(403)
end
end
context 'guest user' do
+ before { post api(post_url, guest) }
+
it 'should not create commit status' do
- post api(post_url, guest)
expect(response.status).to eq(403)
end
end
context 'unauthorized user' do
+ before { post api(post_url) }
+
it 'should not create commit status' do
- post api(post_url)
expect(response.status).to eq(401)
end
end
end
- def create_user(access_level)
+ def create_user(access_level_trait)
user = create(:user)
- create(:project_member, user: user, project: project, access_level: access_level)
+ create(:project_member, access_level_trait, user: user, project: project)
user
end
end
diff --git a/spec/requests/api/commits_spec.rb b/spec/requests/api/commits_spec.rb
index 49acc3368f4..7ff21175c1b 100644
--- a/spec/requests/api/commits_spec.rb
+++ b/spec/requests/api/commits_spec.rb
@@ -6,8 +6,8 @@ describe API::API, api: true do
let(:user) { create(:user) }
let(:user2) { create(:user) }
let!(:project) { create(:project, creator_id: user.id) }
- let!(:master) { create(:project_member, user: user, project: project, access_level: ProjectMember::MASTER) }
- let!(:guest) { create(:project_member, user: user2, project: project, access_level: ProjectMember::GUEST) }
+ let!(:master) { create(:project_member, :master, user: user, project: project) }
+ let!(:guest) { create(:project_member, :guest, user: user2, project: project) }
let!(:note) { create(:note_on_commit, author: user, project: project, commit_id: project.repository.commit.id, note: 'a comment on a commit') }
let!(:another_note) { create(:note_on_commit, author: user, project: project, commit_id: project.repository.commit.id, note: 'another comment on a commit') }
diff --git a/spec/requests/api/fork_spec.rb b/spec/requests/api/fork_spec.rb
index 3fe7efff5ba..fa94e03ec32 100644
--- a/spec/requests/api/fork_spec.rb
+++ b/spec/requests/api/fork_spec.rb
@@ -12,7 +12,7 @@ describe API::API, api: true do
end
let(:project_user2) do
- create(:project_member, user: user2, project: project, access_level: ProjectMember::GUEST)
+ create(:project_member, :guest, user: user2, project: project)
end
describe 'POST /projects/fork/:id' do
diff --git a/spec/requests/api/internal_spec.rb b/spec/requests/api/internal_spec.rb
index 8d0ae1475c2..22802dd0e05 100644
--- a/spec/requests/api/internal_spec.rb
+++ b/spec/requests/api/internal_spec.rb
@@ -54,6 +54,18 @@ describe API::API, api: true do
project.team << [user, :developer]
end
+ context "git push with project.wiki" do
+ it 'responds with success' do
+ project_wiki = create(:project, name: 'my.wiki', path: 'my.wiki')
+ project_wiki.team << [user, :developer]
+
+ push(key, project_wiki)
+
+ expect(response.status).to eq(200)
+ expect(json_response["status"]).to be_truthy
+ end
+ end
+
context "git pull" do
it do
pull(key, project)
diff --git a/spec/requests/api/project_members_spec.rb b/spec/requests/api/project_members_spec.rb
index 6358f6a2a4a..4301588b16a 100644
--- a/spec/requests/api/project_members_spec.rb
+++ b/spec/requests/api/project_members_spec.rb
@@ -6,8 +6,8 @@ describe API::API, api: true do
let(:user2) { create(:user) }
let(:user3) { create(:user) }
let(:project) { create(:project, creator_id: user.id, namespace: user.namespace) }
- let(:project_member) { create(:project_member, user: user, project: project, access_level: ProjectMember::MASTER) }
- let(:project_member2) { create(:project_member, user: user3, project: project, access_level: ProjectMember::DEVELOPER) }
+ let(:project_member) { create(:project_member, :master, user: user, project: project) }
+ let(:project_member2) { create(:project_member, :developer, user: user3, project: project) }
describe "GET /projects/:id/members" do
before { project_member }
diff --git a/spec/requests/api/projects_spec.rb b/spec/requests/api/projects_spec.rb
index 2a310f3834d..9f2365a4832 100644
--- a/spec/requests/api/projects_spec.rb
+++ b/spec/requests/api/projects_spec.rb
@@ -12,8 +12,8 @@ describe API::API, api: true do
let(:project2) { create(:project, path: 'project2', creator_id: user.id, namespace: user.namespace) }
let(:project3) { create(:project, path: 'project3', creator_id: user.id, namespace: user.namespace) }
let(:snippet) { create(:project_snippet, author: user, project: project, title: 'example') }
- let(:project_member) { create(:project_member, user: user, project: project, access_level: ProjectMember::MASTER) }
- let(:project_member2) { create(:project_member, user: user3, project: project, access_level: ProjectMember::DEVELOPER) }
+ let(:project_member) { create(:project_member, :master, user: user, project: project) }
+ let(:project_member2) { create(:project_member, :developer, user: user3, project: project) }
let(:user4) { create(:user) }
let(:project3) do
create(:project,
diff --git a/spec/requests/api/repositories_spec.rb b/spec/requests/api/repositories_spec.rb
index 4911cdd9da6..7cf4a01d76b 100644
--- a/spec/requests/api/repositories_spec.rb
+++ b/spec/requests/api/repositories_spec.rb
@@ -4,12 +4,13 @@ require 'mime/types'
describe API::API, api: true do
include ApiHelpers
include RepoHelpers
+ include WorkhorseHelpers
let(:user) { create(:user) }
let(:user2) { create(:user) }
let!(:project) { create(:project, creator_id: user.id) }
- let!(:master) { create(:project_member, user: user, project: project, access_level: ProjectMember::MASTER) }
- let!(:guest) { create(:project_member, user: user2, project: project, access_level: ProjectMember::GUEST) }
+ let!(:master) { create(:project_member, :master, user: user, project: project) }
+ let!(:guest) { create(:project_member, :guest, user: user2, project: project) }
describe "GET /projects/:id/repository/tree" do
context "authorized user" do
@@ -91,21 +92,27 @@ describe API::API, api: true do
get api("/projects/#{project.id}/repository/archive", user)
repo_name = project.repository.name.gsub("\.git", "")
expect(response.status).to eq(200)
- expect(json_response['ArchivePath']).to match(/#{repo_name}\-[^\.]+\.tar.gz/)
+ type, params = workhorse_send_data
+ expect(type).to eq('git-archive')
+ expect(params['ArchivePath']).to match(/#{repo_name}\-[^\.]+\.tar.gz/)
end
it "should get the archive.zip" do
get api("/projects/#{project.id}/repository/archive.zip", user)
repo_name = project.repository.name.gsub("\.git", "")
expect(response.status).to eq(200)
- expect(json_response['ArchivePath']).to match(/#{repo_name}\-[^\.]+\.zip/)
+ type, params = workhorse_send_data
+ expect(type).to eq('git-archive')
+ expect(params['ArchivePath']).to match(/#{repo_name}\-[^\.]+\.zip/)
end
it "should get the archive.tar.bz2" do
get api("/projects/#{project.id}/repository/archive.tar.bz2", user)
repo_name = project.repository.name.gsub("\.git", "")
expect(response.status).to eq(200)
- expect(json_response['ArchivePath']).to match(/#{repo_name}\-[^\.]+\.tar.bz2/)
+ type, params = workhorse_send_data
+ expect(type).to eq('git-archive')
+ expect(params['ArchivePath']).to match(/#{repo_name}\-[^\.]+\.tar.bz2/)
end
it "should return 404 for invalid sha" do
diff --git a/spec/requests/api/runners_spec.rb b/spec/requests/api/runners_spec.rb
new file mode 100644
index 00000000000..3af61d4b335
--- /dev/null
+++ b/spec/requests/api/runners_spec.rb
@@ -0,0 +1,464 @@
+require 'spec_helper'
+
+describe API::Runners, api: true do
+ include ApiHelpers
+
+ let(:admin) { create(:user, :admin) }
+ let(:user) { create(:user) }
+ let(:user2) { create(:user) }
+
+ let(:project) { create(:project, creator_id: user.id) }
+ let(:project2) { create(:project, creator_id: user.id) }
+
+ let!(:shared_runner) { create(:ci_runner, :shared) }
+ let!(:unused_specific_runner) { create(:ci_runner) }
+
+ let!(:specific_runner) do
+ create(:ci_runner).tap do |runner|
+ create(:ci_runner_project, runner: runner, project: project)
+ end
+ end
+
+ let!(:two_projects_runner) do
+ create(:ci_runner).tap do |runner|
+ create(:ci_runner_project, runner: runner, project: project)
+ create(:ci_runner_project, runner: runner, project: project2)
+ end
+ end
+
+ before do
+ # Set project access for users
+ create(:project_member, :master, user: user, project: project)
+ create(:project_member, :master, user: user, project: project2)
+ create(:project_member, :reporter, user: user2, project: project)
+ end
+
+ describe 'GET /runners' do
+ context 'authorized user' do
+ it 'should return user available runners' do
+ get api('/runners', user)
+ shared = json_response.any?{ |r| r['is_shared'] }
+
+ expect(response.status).to eq(200)
+ expect(json_response).to be_an Array
+ expect(shared).to be_falsey
+ end
+
+ it 'should filter runners by scope' do
+ get api('/runners?scope=active', user)
+ shared = json_response.any?{ |r| r['is_shared'] }
+
+ expect(response.status).to eq(200)
+ expect(json_response).to be_an Array
+ expect(shared).to be_falsey
+ end
+
+ it 'should avoid filtering if scope is invalid' do
+ get api('/runners?scope=unknown', user)
+ expect(response.status).to eq(400)
+ end
+ end
+
+ context 'unauthorized user' do
+ it 'should not return runners' do
+ get api('/runners')
+
+ expect(response.status).to eq(401)
+ end
+ end
+ end
+
+ describe 'GET /runners/all' do
+ context 'authorized user' do
+ context 'with admin privileges' do
+ it 'should return all runners' do
+ get api('/runners/all', admin)
+ shared = json_response.any?{ |r| r['is_shared'] }
+
+ expect(response.status).to eq(200)
+ expect(json_response).to be_an Array
+ expect(shared).to be_truthy
+ end
+ end
+
+ context 'without admin privileges' do
+ it 'should not return runners list' do
+ get api('/runners/all', user)
+
+ expect(response.status).to eq(403)
+ end
+ end
+
+ it 'should filter runners by scope' do
+ get api('/runners/all?scope=specific', admin)
+ shared = json_response.any?{ |r| r['is_shared'] }
+
+ expect(response.status).to eq(200)
+ expect(json_response).to be_an Array
+ expect(shared).to be_falsey
+ end
+
+ it 'should avoid filtering if scope is invalid' do
+ get api('/runners?scope=unknown', admin)
+ expect(response.status).to eq(400)
+ end
+ end
+
+ context 'unauthorized user' do
+ it 'should not return runners' do
+ get api('/runners')
+
+ expect(response.status).to eq(401)
+ end
+ end
+ end
+
+ describe 'GET /runners/:id' do
+ context 'admin user' do
+ context 'when runner is shared' do
+ it "should return runner's details" do
+ get api("/runners/#{shared_runner.id}", admin)
+
+ expect(response.status).to eq(200)
+ expect(json_response['description']).to eq(shared_runner.description)
+ end
+ end
+
+ context 'when runner is not shared' do
+ it "should return runner's details" do
+ get api("/runners/#{specific_runner.id}", admin)
+
+ expect(response.status).to eq(200)
+ expect(json_response['description']).to eq(specific_runner.description)
+ end
+ end
+
+ it 'should return 404 if runner does not exists' do
+ get api('/runners/9999', admin)
+
+ expect(response.status).to eq(404)
+ end
+ end
+
+ context "runner project's administrative user" do
+ context 'when runner is not shared' do
+ it "should return runner's details" do
+ get api("/runners/#{specific_runner.id}", user)
+
+ expect(response.status).to eq(200)
+ expect(json_response['description']).to eq(specific_runner.description)
+ end
+ end
+
+ context 'when runner is shared' do
+ it "should return runner's details" do
+ get api("/runners/#{shared_runner.id}", user)
+
+ expect(response.status).to eq(200)
+ expect(json_response['description']).to eq(shared_runner.description)
+ end
+ end
+ end
+
+ context 'other authorized user' do
+ it "should not return runner's details" do
+ get api("/runners/#{specific_runner.id}", user2)
+
+ expect(response.status).to eq(403)
+ end
+ end
+
+ context 'unauthorized user' do
+ it "should not return runner's details" do
+ get api("/runners/#{specific_runner.id}")
+
+ expect(response.status).to eq(401)
+ end
+ end
+ end
+
+ describe 'PUT /runners/:id' do
+ context 'admin user' do
+ context 'when runner is shared' do
+ it 'should update runner' do
+ description = shared_runner.description
+ active = shared_runner.active
+
+ put api("/runners/#{shared_runner.id}", admin), description: "#{description}_updated", active: !active,
+ tag_list: ['ruby2.1', 'pgsql', 'mysql']
+ shared_runner.reload
+
+ expect(response.status).to eq(200)
+ expect(shared_runner.description).to eq("#{description}_updated")
+ expect(shared_runner.active).to eq(!active)
+ expect(shared_runner.tag_list).to include('ruby2.1', 'pgsql', 'mysql')
+ end
+ end
+
+ context 'when runner is not shared' do
+ it 'should update runner' do
+ description = specific_runner.description
+ put api("/runners/#{specific_runner.id}", admin), description: 'test'
+ specific_runner.reload
+
+ expect(response.status).to eq(200)
+ expect(specific_runner.description).to eq('test')
+ expect(specific_runner.description).not_to eq(description)
+ end
+ end
+
+ it 'should return 404 if runner does not exists' do
+ put api('/runners/9999', admin), description: 'test'
+
+ expect(response.status).to eq(404)
+ end
+ end
+
+ context 'authorized user' do
+ context 'when runner is shared' do
+ it 'should not update runner' do
+ put api("/runners/#{shared_runner.id}", user)
+
+ expect(response.status).to eq(403)
+ end
+ end
+
+ context 'when runner is not shared' do
+ it 'should not update runner without access to it' do
+ put api("/runners/#{specific_runner.id}", user2)
+
+ expect(response.status).to eq(403)
+ end
+
+ it 'should update runner with access to it' do
+ description = specific_runner.description
+ put api("/runners/#{specific_runner.id}", admin), description: 'test'
+ specific_runner.reload
+
+ expect(response.status).to eq(200)
+ expect(specific_runner.description).to eq('test')
+ expect(specific_runner.description).not_to eq(description)
+ end
+ end
+ end
+
+ context 'unauthorized user' do
+ it 'should not delete runner' do
+ put api("/runners/#{specific_runner.id}")
+
+ expect(response.status).to eq(401)
+ end
+ end
+ end
+
+ describe 'DELETE /runners/:id' do
+ context 'admin user' do
+ context 'when runner is shared' do
+ it 'should delete runner' do
+ expect do
+ delete api("/runners/#{shared_runner.id}", admin)
+ end.to change{ Ci::Runner.shared.count }.by(-1)
+ expect(response.status).to eq(200)
+ end
+ end
+
+ context 'when runner is not shared' do
+ it 'should delete unused runner' do
+ expect do
+ delete api("/runners/#{unused_specific_runner.id}", admin)
+ end.to change{ Ci::Runner.specific.count }.by(-1)
+ expect(response.status).to eq(200)
+ end
+
+ it 'should delete used runner' do
+ expect do
+ delete api("/runners/#{specific_runner.id}", admin)
+ end.to change{ Ci::Runner.specific.count }.by(-1)
+ expect(response.status).to eq(200)
+ end
+ end
+
+ it 'should return 404 if runner does not exists' do
+ delete api('/runners/9999', admin)
+
+ expect(response.status).to eq(404)
+ end
+ end
+
+ context 'authorized user' do
+ context 'when runner is shared' do
+ it 'should not delete runner' do
+ delete api("/runners/#{shared_runner.id}", user)
+ expect(response.status).to eq(403)
+ end
+ end
+
+ context 'when runner is not shared' do
+ it 'should not delete runner without access to it' do
+ delete api("/runners/#{specific_runner.id}", user2)
+ expect(response.status).to eq(403)
+ end
+
+ it 'should not delete runner with more than one associated project' do
+ delete api("/runners/#{two_projects_runner.id}", user)
+ expect(response.status).to eq(403)
+ end
+
+ it 'should delete runner for one owned project' do
+ expect do
+ delete api("/runners/#{specific_runner.id}", user)
+ end.to change{ Ci::Runner.specific.count }.by(-1)
+ expect(response.status).to eq(200)
+ end
+ end
+ end
+
+ context 'unauthorized user' do
+ it 'should not delete runner' do
+ delete api("/runners/#{specific_runner.id}")
+
+ expect(response.status).to eq(401)
+ end
+ end
+ end
+
+ describe 'GET /projects/:id/runners' do
+ context 'authorized user with master privileges' do
+ it "should return project's runners" do
+ get api("/projects/#{project.id}/runners", user)
+ shared = json_response.any?{ |r| r['is_shared'] }
+
+ expect(response.status).to eq(200)
+ expect(json_response).to be_an Array
+ expect(shared).to be_truthy
+ end
+ end
+
+ context 'authorized user without master privileges' do
+ it "should not return project's runners" do
+ get api("/projects/#{project.id}/runners", user2)
+
+ expect(response.status).to eq(403)
+ end
+ end
+
+ context 'unauthorized user' do
+ it "should not return project's runners" do
+ get api("/projects/#{project.id}/runners")
+
+ expect(response.status).to eq(401)
+ end
+ end
+ end
+
+ describe 'POST /projects/:id/runners' do
+ context 'authorized user' do
+ it 'should enable specific runner' do
+ specific_runner2 = create(:ci_runner).tap do |runner|
+ create(:ci_runner_project, runner: runner, project: project2)
+ end
+
+ expect do
+ post api("/projects/#{project.id}/runners", user), runner_id: specific_runner2.id
+ end.to change{ project.runners.count }.by(+1)
+ expect(response.status).to eq(201)
+ end
+
+ it 'should avoid changes when enabling already enabled runner' do
+ expect do
+ post api("/projects/#{project.id}/runners", user), runner_id: specific_runner.id
+ end.to change{ project.runners.count }.by(0)
+ expect(response.status).to eq(201)
+ end
+
+ it 'should not enable shared runner' do
+ post api("/projects/#{project.id}/runners", user), runner_id: shared_runner.id
+
+ expect(response.status).to eq(403)
+ end
+
+ context 'user is admin' do
+ it 'should enable any specific runner' do
+ expect do
+ post api("/projects/#{project.id}/runners", admin), runner_id: unused_specific_runner.id
+ end.to change{ project.runners.count }.by(+1)
+ expect(response.status).to eq(201)
+ end
+ end
+
+ context 'user is not admin' do
+ it 'should not enable runner without access to' do
+ post api("/projects/#{project.id}/runners", user), runner_id: unused_specific_runner.id
+
+ expect(response.status).to eq(403)
+ end
+ end
+
+ it 'should raise an error when no runner_id param is provided' do
+ post api("/projects/#{project.id}/runners", admin)
+
+ expect(response.status).to eq(400)
+ end
+ end
+
+ context 'authorized user without permissions' do
+ it 'should not enable runner' do
+ post api("/projects/#{project.id}/runners", user2)
+
+ expect(response.status).to eq(403)
+ end
+ end
+
+ context 'unauthorized user' do
+ it 'should not enable runner' do
+ post api("/projects/#{project.id}/runners")
+
+ expect(response.status).to eq(401)
+ end
+ end
+ end
+
+ describe 'DELETE /projects/:id/runners/:runner_id' do
+ context 'authorized user' do
+ context 'when runner have more than one associated projects' do
+ it "should disable project's runner" do
+ expect do
+ delete api("/projects/#{project.id}/runners/#{two_projects_runner.id}", user)
+ end.to change{ project.runners.count }.by(-1)
+ expect(response.status).to eq(200)
+ end
+ end
+
+ context 'when runner have one associated projects' do
+ it "should not disable project's runner" do
+ expect do
+ delete api("/projects/#{project.id}/runners/#{specific_runner.id}", user)
+ end.to change{ project.runners.count }.by(0)
+ expect(response.status).to eq(403)
+ end
+ end
+
+ it 'should return 404 is runner is not found' do
+ delete api("/projects/#{project.id}/runners/9999", user)
+
+ expect(response.status).to eq(404)
+ end
+ end
+
+ context 'authorized user without permissions' do
+ it "should not disable project's runner" do
+ delete api("/projects/#{project.id}/runners/#{specific_runner.id}", user2)
+
+ expect(response.status).to eq(403)
+ end
+ end
+
+ context 'unauthorized user' do
+ it "should not disable project's runner" do
+ delete api("/projects/#{project.id}/runners/#{specific_runner.id}")
+
+ expect(response.status).to eq(401)
+ end
+ end
+ end
+end
diff --git a/spec/requests/api/tags_spec.rb b/spec/requests/api/tags_spec.rb
index f966e38cd3e..a15be07ed57 100644
--- a/spec/requests/api/tags_spec.rb
+++ b/spec/requests/api/tags_spec.rb
@@ -8,8 +8,8 @@ describe API::API, api: true do
let(:user) { create(:user) }
let(:user2) { create(:user) }
let!(:project) { create(:project, creator_id: user.id) }
- let!(:master) { create(:project_member, user: user, project: project, access_level: ProjectMember::MASTER) }
- let!(:guest) { create(:project_member, user: user2, project: project, access_level: ProjectMember::GUEST) }
+ let!(:master) { create(:project_member, :master, user: user, project: project) }
+ let!(:guest) { create(:project_member, :guest, user: user2, project: project) }
describe "GET /projects/:id/repository/tags" do
let(:tag_name) { project.repository.tag_names.sort.reverse.first }
diff --git a/spec/requests/api/triggers_spec.rb b/spec/requests/api/triggers_spec.rb
index 2a86b60bc4d..0510b77a39b 100644
--- a/spec/requests/api/triggers_spec.rb
+++ b/spec/requests/api/triggers_spec.rb
@@ -8,8 +8,8 @@ describe API::API do
let!(:trigger_token) { 'secure_token' }
let!(:trigger_token_2) { 'secure_token_2' }
let!(:project) { create(:project, creator_id: user.id) }
- let!(:master) { create(:project_member, user: user, project: project, access_level: ProjectMember::MASTER) }
- let!(:developer) { create(:project_member, user: user2, project: project, access_level: ProjectMember::DEVELOPER) }
+ let!(:master) { create(:project_member, :master, user: user, project: project) }
+ let!(:developer) { create(:project_member, :developer, user: user2, project: project) }
let!(:trigger) { create(:ci_trigger, project: project, token: trigger_token) }
let!(:trigger2) { create(:ci_trigger, project: project, token: trigger_token_2) }
let!(:trigger_request) { create(:ci_trigger_request, trigger: trigger, created_at: '2015-01-01 12:13:14') }
diff --git a/spec/requests/api/variables_spec.rb b/spec/requests/api/variables_spec.rb
index 9744729ba0c..b1e1053d037 100644
--- a/spec/requests/api/variables_spec.rb
+++ b/spec/requests/api/variables_spec.rb
@@ -6,8 +6,8 @@ describe API::API, api: true do
let(:user) { create(:user) }
let(:user2) { create(:user) }
let!(:project) { create(:project, creator_id: user.id) }
- let!(:master) { create(:project_member, user: user, project: project, access_level: ProjectMember::MASTER) }
- let!(:developer) { create(:project_member, user: user2, project: project, access_level: ProjectMember::DEVELOPER) }
+ let!(:master) { create(:project_member, :master, user: user, project: project) }
+ let!(:developer) { create(:project_member, :developer, user: user2, project: project) }
let!(:variable) { create(:ci_variable, project: project) }
describe 'GET /projects/:id/variables' do
diff --git a/spec/requests/ci/api/builds_spec.rb b/spec/requests/ci/api/builds_spec.rb
index 01b369720ca..57d7eb927fd 100644
--- a/spec/requests/ci/api/builds_spec.rb
+++ b/spec/requests/ci/api/builds_spec.rb
@@ -131,20 +131,28 @@ describe Ci::API::API do
end
describe "PUT /builds/:id" do
- let(:commit) { FactoryGirl.create(:ci_commit, project: project)}
- let(:build) { FactoryGirl.create(:ci_build, commit: commit, runner_id: runner.id) }
+ let(:commit) {create(:ci_commit, project: project)}
+ let(:build) { create(:ci_build, :trace, commit: commit, runner_id: runner.id) }
- it "should update a running build" do
+ before do
build.run!
put ci_api("/builds/#{build.id}"), token: runner.token
+ end
+
+ it "should update a running build" do
expect(response.status).to eq(200)
end
- it 'Should not override trace information when no trace is given' do
- build.run!
- build.update!(trace: 'hello_world')
- put ci_api("/builds/#{build.id}"), token: runner.token
- expect(build.reload.trace).to eq 'hello_world'
+ it 'should not override trace information when no trace is given' do
+ expect(build.reload.trace).to eq 'BUILD TRACE'
+ end
+
+ context 'build has been erased' do
+ let(:build) { create(:ci_build, runner_id: runner.id, erased_at: Time.now) }
+
+ it 'should respond with forbidden' do
+ expect(response.status).to eq 403
+ end
end
end
@@ -191,9 +199,10 @@ describe Ci::API::API do
end
end
- context 'token is invalid' do
- it 'should respond with forbidden'do
- post authorize_url, { token: 'invalid', filesize: 100 }
+ context 'authorization token is invalid' do
+ before { post authorize_url, { token: 'invalid', filesize: 100 } }
+
+ it 'should respond with forbidden' do
expect(response.status).to eq(403)
end
end
@@ -206,6 +215,15 @@ describe Ci::API::API do
allow(ArtifactUploader).to receive(:artifacts_upload_path).and_return('/')
end
+ context 'build has been erased' do
+ let(:build) { create(:ci_build, erased_at: Time.now) }
+ before { upload_artifacts(file_upload, headers_with_token) }
+
+ it 'should respond with forbidden' do
+ expect(response.status).to eq 403
+ end
+ end
+
context "should post artifact to running build" do
it "uses regual file post" do
upload_artifacts(file_upload, headers_with_token, false)
@@ -234,7 +252,9 @@ describe Ci::API::API do
let(:stored_artifacts_file) { build.reload.artifacts_file.file }
let(:stored_metadata_file) { build.reload.artifacts_metadata.file }
- before { post(post_url, post_data, headers_with_token) }
+ before do
+ post(post_url, post_data, headers_with_token)
+ end
context 'post data accelerated by workhorse is correct' do
let(:post_data) do
diff --git a/spec/routing/routing_spec.rb b/spec/routing/routing_spec.rb
index dfa18f69e05..1527eddfa48 100644
--- a/spec/routing/routing_spec.rb
+++ b/spec/routing/routing_spec.rb
@@ -137,7 +137,6 @@ end
# keys GET /keys(.:format) keys#index
# POST /keys(.:format) keys#create
-# new_key GET /keys/new(.:format) keys#new
# edit_key GET /keys/:id/edit(.:format) keys#edit
# key GET /keys/:id(.:format) keys#show
# PUT /keys/:id(.:format) keys#update
@@ -151,10 +150,6 @@ describe Profiles::KeysController, "routing" do
expect(post("/profile/keys")).to route_to('profiles/keys#create')
end
- it "to #new" do
- expect(get("/profile/keys/new")).to route_to('profiles/keys#new')
- end
-
it "to #edit" do
expect(get("/profile/keys/1/edit")).to route_to('profiles/keys#edit', id: '1')
end
diff --git a/spec/services/archive_repository_service_spec.rb b/spec/services/archive_repository_service_spec.rb
deleted file mode 100644
index bd871605c66..00000000000
--- a/spec/services/archive_repository_service_spec.rb
+++ /dev/null
@@ -1,25 +0,0 @@
-require 'spec_helper'
-
-describe ArchiveRepositoryService, services: true do
- let(:project) { create(:project) }
- subject { ArchiveRepositoryService.new(project, "master", "zip") }
-
- describe "#execute" do
- it "cleans old archives" do
- expect(RepositoryArchiveCacheWorker).to receive(:perform_async)
-
- subject.execute(timeout: 0.0)
- end
-
- context "when the repository doesn't have an archive file path" do
- before do
- allow(project.repository).to receive(:archive_metadata).and_return(Hash.new)
- end
-
- it "raises an error" do
- expect { subject.execute(timeout: 0.0) }.to raise_error(RuntimeError)
- end
- end
-
- end
-end
diff --git a/spec/services/ci/create_builds_service_spec.rb b/spec/services/ci/create_builds_service_spec.rb
new file mode 100644
index 00000000000..1fca3628686
--- /dev/null
+++ b/spec/services/ci/create_builds_service_spec.rb
@@ -0,0 +1,28 @@
+require 'spec_helper'
+
+describe Ci::CreateBuildsService, services: true do
+ let(:commit) { create(:ci_commit) }
+ let(:user) { create(:user) }
+
+ describe '#execute' do
+ # Using stubbed .gitlab-ci.yml created in commit factory
+ #
+
+ subject do
+ described_class.new.execute(commit, 'test', 'master', nil, user, nil, status)
+ end
+
+ context 'next builds available' do
+ let(:status) { 'success' }
+
+ it { is_expected.to be_an_instance_of Array }
+ it { is_expected.to all(be_an_instance_of Ci::Build) }
+ end
+
+ context 'builds skipped' do
+ let(:status) { 'skipped' }
+
+ it { is_expected.to be_empty }
+ end
+ end
+end
diff --git a/spec/services/git_push_service_spec.rb b/spec/services/git_push_service_spec.rb
index eb3a5fe43f5..994585fb32c 100644
--- a/spec/services/git_push_service_spec.rb
+++ b/spec/services/git_push_service_spec.rb
@@ -5,7 +5,6 @@ describe GitPushService, services: true do
let(:user) { create :user }
let(:project) { create :project }
- let(:service) { GitPushService.new }
before do
@blankrev = Gitlab::Git::BLANK_SHA
@@ -15,10 +14,17 @@ describe GitPushService, services: true do
end
describe 'Push branches' do
+
+ let(:oldrev) { @oldrev }
+ let(:newrev) { @newrev }
+
+ subject do
+ execute_service(project, user, oldrev, newrev, @ref )
+ end
+
context 'new branch' do
- subject do
- service.execute(project, user, @blankrev, @newrev, @ref)
- end
+
+ let(:oldrev) { @blankrev }
it { is_expected.to be_truthy }
@@ -36,9 +42,6 @@ describe GitPushService, services: true do
end
context 'existing branch' do
- subject do
- service.execute(project, user, @oldrev, @newrev, @ref)
- end
it { is_expected.to be_truthy }
@@ -50,9 +53,8 @@ describe GitPushService, services: true do
end
context 'rm branch' do
- subject do
- service.execute(project, user, @oldrev, @blankrev, @ref)
- end
+
+ let(:newrev) { @blankrev }
it { is_expected.to be_truthy }
@@ -72,7 +74,7 @@ describe GitPushService, services: true do
describe "Git Push Data" do
before do
- service.execute(project, user, @oldrev, @newrev, @ref)
+ service = execute_service(project, user, @oldrev, @newrev, @ref )
@push_data = service.push_data
@commit = project.commit(@newrev)
end
@@ -134,20 +136,21 @@ describe GitPushService, services: true do
describe "Push Event" do
before do
- service.execute(project, user, @oldrev, @newrev, @ref)
+ service = execute_service(project, user, @oldrev, @newrev, @ref )
@event = Event.last
+ @push_data = service.push_data
end
it { expect(@event).not_to be_nil }
it { expect(@event.project).to eq(project) }
it { expect(@event.action).to eq(Event::PUSHED) }
- it { expect(@event.data).to eq(service.push_data) }
+ it { expect(@event.data).to eq(@push_data) }
context "Updates merge requests" do
it "when pushing a new branch for the first time" do
expect(project).to receive(:update_merge_requests).
with(@blankrev, 'newrev', 'refs/heads/master', user)
- service.execute(project, user, @blankrev, 'newrev', 'refs/heads/master')
+ execute_service(project, user, @blankrev, 'newrev', 'refs/heads/master' )
end
end
end
@@ -158,7 +161,7 @@ describe GitPushService, services: true do
expect(project).to receive(:execute_hooks)
expect(project.default_branch).to eq("master")
expect(project.protected_branches).to receive(:create).with({ name: "master", developers_can_push: false })
- service.execute(project, user, @blankrev, 'newrev', 'refs/heads/master')
+ execute_service(project, user, @blankrev, 'newrev', 'refs/heads/master' )
end
it "when pushing a branch for the first time with default branch protection disabled" do
@@ -167,7 +170,7 @@ describe GitPushService, services: true do
expect(project).to receive(:execute_hooks)
expect(project.default_branch).to eq("master")
expect(project.protected_branches).not_to receive(:create)
- service.execute(project, user, @blankrev, 'newrev', 'refs/heads/master')
+ execute_service(project, user, @blankrev, 'newrev', 'refs/heads/master' )
end
it "when pushing a branch for the first time with default branch protection set to 'developers can push'" do
@@ -176,12 +179,12 @@ describe GitPushService, services: true do
expect(project).to receive(:execute_hooks)
expect(project.default_branch).to eq("master")
expect(project.protected_branches).to receive(:create).with({ name: "master", developers_can_push: true })
- service.execute(project, user, @blankrev, 'newrev', 'refs/heads/master')
+ execute_service(project, user, @blankrev, 'newrev', 'refs/heads/master' )
end
it "when pushing new commits to existing branch" do
expect(project).to receive(:execute_hooks)
- service.execute(project, user, 'oldrev', 'newrev', 'refs/heads/master')
+ execute_service(project, user, 'oldrev', 'newrev', 'refs/heads/master' )
end
end
end
@@ -204,7 +207,7 @@ describe GitPushService, services: true do
it "creates a note if a pushed commit mentions an issue" do
expect(SystemNoteService).to receive(:cross_reference).with(issue, commit, commit_author)
- service.execute(project, user, @oldrev, @newrev, @ref)
+ execute_service(project, user, @oldrev, @newrev, @ref )
end
it "only creates a cross-reference note if one doesn't already exist" do
@@ -212,7 +215,7 @@ describe GitPushService, services: true do
expect(SystemNoteService).not_to receive(:cross_reference).with(issue, commit, commit_author)
- service.execute(project, user, @oldrev, @newrev, @ref)
+ execute_service(project, user, @oldrev, @newrev, @ref )
end
it "defaults to the pushing user if the commit's author is not known" do
@@ -222,7 +225,7 @@ describe GitPushService, services: true do
)
expect(SystemNoteService).to receive(:cross_reference).with(issue, commit, user)
- service.execute(project, user, @oldrev, @newrev, @ref)
+ execute_service(project, user, @oldrev, @newrev, @ref )
end
it "finds references in the first push to a non-default branch" do
@@ -231,7 +234,7 @@ describe GitPushService, services: true do
expect(SystemNoteService).to receive(:cross_reference).with(issue, commit, commit_author)
- service.execute(project, user, @blankrev, @newrev, 'refs/heads/other')
+ execute_service(project, user, @blankrev, @newrev, 'refs/heads/other' )
end
end
@@ -255,18 +258,18 @@ describe GitPushService, services: true do
context "to default branches" do
it "closes issues" do
- service.execute(project, user, @oldrev, @newrev, @ref)
+ execute_service(project, user, @oldrev, @newrev, @ref )
expect(Issue.find(issue.id)).to be_closed
end
it "adds a note indicating that the issue is now closed" do
expect(SystemNoteService).to receive(:change_status).with(issue, project, commit_author, "closed", closing_commit)
- service.execute(project, user, @oldrev, @newrev, @ref)
+ execute_service(project, user, @oldrev, @newrev, @ref )
end
it "doesn't create additional cross-reference notes" do
expect(SystemNoteService).not_to receive(:cross_reference)
- service.execute(project, user, @oldrev, @newrev, @ref)
+ execute_service(project, user, @oldrev, @newrev, @ref )
end
it "doesn't close issues when external issue tracker is in use" do
@@ -274,7 +277,7 @@ describe GitPushService, services: true do
# The push still shouldn't create cross-reference notes.
expect do
- service.execute(project, user, @oldrev, @newrev, 'refs/heads/hurf')
+ execute_service(project, user, @oldrev, @newrev, 'refs/heads/hurf' )
end.not_to change { Note.where(project_id: project.id, system: true).count }
end
end
@@ -287,11 +290,11 @@ describe GitPushService, services: true do
it "creates cross-reference notes" do
expect(SystemNoteService).to receive(:cross_reference).with(issue, closing_commit, commit_author)
- service.execute(project, user, @oldrev, @newrev, @ref)
+ execute_service(project, user, @oldrev, @newrev, @ref )
end
it "doesn't close issues" do
- service.execute(project, user, @oldrev, @newrev, @ref)
+ execute_service(project, user, @oldrev, @newrev, @ref )
expect(Issue.find(issue.id)).to be_opened
end
end
@@ -328,7 +331,7 @@ describe GitPushService, services: true do
let(:message) { "this is some work.\n\nrelated to JIRA-1" }
it "should initiate one api call to jira server to mention the issue" do
- service.execute(project, user, @oldrev, @newrev, @ref)
+ execute_service(project, user, @oldrev, @newrev, @ref )
expect(WebMock).to have_requested(:post, jira_api_comment_url).with(
body: /mentioned this issue in/
@@ -346,7 +349,7 @@ describe GitPushService, services: true do
}
}.to_json
- service.execute(project, user, @oldrev, @newrev, @ref)
+ execute_service(project, user, @oldrev, @newrev, @ref )
expect(WebMock).to have_requested(:post, jira_api_transition_url).with(
body: transition_body
).once
@@ -357,7 +360,7 @@ describe GitPushService, services: true do
body: "Issue solved with [#{closing_commit.id}|http://localhost/#{project.path_with_namespace}/commit/#{closing_commit.id}]."
}.to_json
- service.execute(project, user, @oldrev, @newrev, @ref)
+ execute_service(project, user, @oldrev, @newrev, @ref )
expect(WebMock).to have_requested(:post, jira_api_comment_url).with(
body: comment_body
).once
@@ -376,7 +379,13 @@ describe GitPushService, services: true do
end
it 'push to first branch updates HEAD' do
- service.execute(project, user, @blankrev, @newrev, new_ref)
+ execute_service(project, user, @blankrev, @newrev, new_ref )
end
end
+
+ def execute_service(project, user, oldrev, newrev, ref)
+ service = described_class.new(project, user, oldrev: oldrev, newrev: newrev, ref: ref )
+ service.execute
+ service
+ end
end
diff --git a/spec/services/issues/close_service_spec.rb b/spec/services/issues/close_service_spec.rb
index 3a8daf28f5e..62b25709a5d 100644
--- a/spec/services/issues/close_service_spec.rb
+++ b/spec/services/issues/close_service_spec.rb
@@ -5,6 +5,7 @@ describe Issues::CloseService, services: true do
let(:user2) { create(:user) }
let(:issue) { create(:issue, assignee: user2) }
let(:project) { issue.project }
+ let!(:todo) { create(:todo, :assigned, user: user, project: project, target: issue, author: user2) }
before do
project.team << [user, :master]
@@ -32,6 +33,10 @@ describe Issues::CloseService, services: true do
note = @issue.notes.last
expect(note.note).to include "Status changed to closed"
end
+
+ it 'marks todos as done' do
+ expect(todo.reload).to be_done
+ end
end
context "external issue tracker" do
@@ -42,6 +47,7 @@ describe Issues::CloseService, services: true do
it { expect(@issue).to be_valid }
it { expect(@issue).to be_opened }
+ it { expect(todo.reload).to be_pending }
end
end
end
diff --git a/spec/services/issues/create_service_spec.rb b/spec/services/issues/create_service_spec.rb
index 2148d091a57..5e7915db7e1 100644
--- a/spec/services/issues/create_service_spec.rb
+++ b/spec/services/issues/create_service_spec.rb
@@ -3,14 +3,18 @@ require 'spec_helper'
describe Issues::CreateService, services: true do
let(:project) { create(:empty_project) }
let(:user) { create(:user) }
+ let(:assignee) { create(:user) }
describe :execute do
- context "valid params" do
+ context 'valid params' do
before do
project.team << [user, :master]
+ project.team << [assignee, :master]
+
opts = {
title: 'Awesome issue',
- description: 'please fix'
+ description: 'please fix',
+ assignee: assignee
}
@issue = Issues::CreateService.new(project, user, opts).execute
@@ -18,6 +22,21 @@ describe Issues::CreateService, services: true do
it { expect(@issue).to be_valid }
it { expect(@issue.title).to eq('Awesome issue') }
+ it { expect(@issue.assignee).to eq assignee }
+
+ it 'creates a pending todo for new assignee' do
+ attributes = {
+ project: project,
+ author: user,
+ user: assignee,
+ target_id: @issue.id,
+ target_type: @issue.class.name,
+ action: Todo::ASSIGNED,
+ state: :pending
+ }
+
+ expect(Todo.where(attributes).count).to eq 1
+ end
end
end
end
diff --git a/spec/services/issues/update_service_spec.rb b/spec/services/issues/update_service_spec.rb
index 87da0e9618b..e579e49dfa7 100644
--- a/spec/services/issues/update_service_spec.rb
+++ b/spec/services/issues/update_service_spec.rb
@@ -80,6 +80,74 @@ describe Issues::UpdateService, services: true do
end
end
+ context 'todos' do
+ let!(:todo) { create(:todo, :assigned, user: user, project: project, target: issue, author: user2) }
+
+ context 'when the title change' do
+ before do
+ update_issue({ title: 'New title' })
+ end
+
+ it 'marks pending todos as done' do
+ expect(todo.reload.done?).to eq true
+ end
+ end
+
+ context 'when the description change' do
+ before do
+ update_issue({ description: 'Also please fix' })
+ end
+
+ it 'marks todos as done' do
+ expect(todo.reload.done?).to eq true
+ end
+ end
+
+ context 'when is reassigned' do
+ before do
+ update_issue({ assignee: user2 })
+ end
+
+ it 'marks previous assignee todos as done' do
+ expect(todo.reload.done?).to eq true
+ end
+
+ it 'creates a todo for new assignee' do
+ attributes = {
+ project: project,
+ author: user,
+ user: user2,
+ target_id: issue.id,
+ target_type: issue.class.name,
+ action: Todo::ASSIGNED,
+ state: :pending
+ }
+
+ expect(Todo.where(attributes).count).to eq 1
+ end
+ end
+
+ context 'when the milestone change' do
+ before do
+ update_issue({ milestone: create(:milestone) })
+ end
+
+ it 'marks todos as done' do
+ expect(todo.reload.done?).to eq true
+ end
+ end
+
+ context 'when the labels change' do
+ before do
+ update_issue({ label_ids: [label.id] })
+ end
+
+ it 'marks todos as done' do
+ expect(todo.reload.done?).to eq true
+ end
+ end
+ end
+
context 'when Issue has tasks' do
before { update_issue({ description: "- [ ] Task 1\n- [ ] Task 2" }) }
@@ -144,6 +212,5 @@ describe Issues::UpdateService, services: true do
end
end
end
-
end
end
diff --git a/spec/services/merge_requests/close_service_spec.rb b/spec/services/merge_requests/close_service_spec.rb
index 50d0c288790..8443a00e70c 100644
--- a/spec/services/merge_requests/close_service_spec.rb
+++ b/spec/services/merge_requests/close_service_spec.rb
@@ -5,6 +5,7 @@ describe MergeRequests::CloseService, services: true do
let(:user2) { create(:user) }
let(:merge_request) { create(:merge_request, assignee: user2) }
let(:project) { merge_request.project }
+ let!(:todo) { create(:todo, :assigned, user: user, project: project, target: merge_request, author: user2) }
before do
project.team << [user, :master]
@@ -41,6 +42,10 @@ describe MergeRequests::CloseService, services: true do
note = @merge_request.notes.last
expect(note.note).to include 'Status changed to closed'
end
+
+ it 'marks todos as done' do
+ expect(todo.reload).to be_done
+ end
end
end
end
diff --git a/spec/services/merge_requests/create_service_spec.rb b/spec/services/merge_requests/create_service_spec.rb
index be8f1676eeb..120f4d6a669 100644
--- a/spec/services/merge_requests/create_service_spec.rb
+++ b/spec/services/merge_requests/create_service_spec.rb
@@ -3,6 +3,7 @@ require 'spec_helper'
describe MergeRequests::CreateService, services: true do
let(:project) { create(:project) }
let(:user) { create(:user) }
+ let(:assignee) { create(:user) }
describe :execute do
context 'valid params' do
@@ -14,10 +15,12 @@ describe MergeRequests::CreateService, services: true do
target_branch: 'master'
}
end
+
let(:service) { MergeRequests::CreateService.new(project, user, opts) }
before do
project.team << [user, :master]
+ project.team << [assignee, :developer]
allow(service).to receive(:execute_hooks)
@merge_request = service.execute
@@ -25,10 +28,49 @@ describe MergeRequests::CreateService, services: true do
it { expect(@merge_request).to be_valid }
it { expect(@merge_request.title).to eq('Awesome merge_request') }
+ it { expect(@merge_request.assignee).to be_nil }
it 'should execute hooks with default action' do
expect(service).to have_received(:execute_hooks).with(@merge_request)
end
+
+ it 'does not creates todos' do
+ attributes = {
+ project: project,
+ target_id: @merge_request.id,
+ target_type: @merge_request.class.name
+ }
+
+ expect(Todo.where(attributes).count).to be_zero
+ end
+
+ context 'when merge request is assigned to someone' do
+ let(:opts) do
+ {
+ title: 'Awesome merge_request',
+ description: 'please fix',
+ source_branch: 'feature',
+ target_branch: 'master',
+ assignee: assignee
+ }
+ end
+
+ it { expect(@merge_request.assignee).to eq assignee }
+
+ it 'creates a todo for new assignee' do
+ attributes = {
+ project: project,
+ author: user,
+ user: assignee,
+ target_id: @merge_request.id,
+ target_type: @merge_request.class.name,
+ action: Todo::ASSIGNED,
+ state: :pending
+ }
+
+ expect(Todo.where(attributes).count).to eq 1
+ end
+ end
end
end
end
diff --git a/spec/services/merge_requests/merge_when_build_succeeds_service_spec.rb b/spec/services/merge_requests/merge_when_build_succeeds_service_spec.rb
index de9fed2b7dd..52a302e0e1a 100644
--- a/spec/services/merge_requests/merge_when_build_succeeds_service_spec.rb
+++ b/spec/services/merge_requests/merge_when_build_succeeds_service_spec.rb
@@ -54,14 +54,84 @@ describe MergeRequests::MergeWhenBuildSucceedsService do
end
describe "#trigger" do
- let(:build) { create(:ci_build, ref: mr_merge_if_green_enabled.source_branch, status: "success") }
+ context 'build with ref' do
+ let(:build) { create(:ci_build, ref: mr_merge_if_green_enabled.source_branch, status: "success") }
- it "merges all merge requests with merge when build succeeds enabled" do
- allow_any_instance_of(MergeRequest).to receive(:ci_commit).and_return(ci_commit)
- allow(ci_commit).to receive(:success?).and_return(true)
+ it "merges all merge requests with merge when build succeeds enabled" do
+ allow_any_instance_of(MergeRequest).to receive(:ci_commit).and_return(ci_commit)
+ allow(ci_commit).to receive(:success?).and_return(true)
+
+ expect(MergeWorker).to receive(:perform_async)
+ service.trigger(build)
+ end
+ end
+
+ context 'triggered by an old build' do
+ let(:old_build) { create(:ci_build, ref: mr_merge_if_green_enabled.source_branch, status: "success") }
+ let(:build) { create(:ci_build, ref: mr_merge_if_green_enabled.source_branch, status: "success") }
+
+ it "merges all merge requests with merge when build succeeds enabled" do
+ allow_any_instance_of(MergeRequest).to receive(:ci_commit).and_return(ci_commit)
+ allow(ci_commit).to receive(:success?).and_return(true)
+ allow(old_build).to receive(:sha).and_return('1234abcdef')
+
+ expect(MergeWorker).to_not receive(:perform_async)
+ service.trigger(old_build)
+ end
+ end
+
+ context 'commit status without ref' do
+ let(:commit_status) { create(:generic_commit_status, status: 'success') }
+
+ before { mr_merge_if_green_enabled }
+
+ it "doesn't merge a requests for status on other branch" do
+ allow(project.repository).to receive(:branch_names_contains).with(commit_status.sha).and_return([])
+
+ expect(MergeWorker).to_not receive(:perform_async)
+ service.trigger(commit_status)
+ end
+
+ it 'discovers branches and merges all merge requests when status is success' do
+ allow(project.repository).to receive(:branch_names_contains).
+ with(commit_status.sha).and_return([mr_merge_if_green_enabled.source_branch])
+ allow(ci_commit).to receive(:success?).and_return(true)
+ allow_any_instance_of(MergeRequest).to receive(:ci_commit).and_return(ci_commit)
+ allow(ci_commit).to receive(:success?).and_return(true)
+
+ expect(MergeWorker).to receive(:perform_async)
+ service.trigger(commit_status)
+ end
+ end
- expect(MergeWorker).to receive(:perform_async)
- service.trigger(build)
+ context 'properly handles multiple stages' do
+ let(:ref) { mr_merge_if_green_enabled.source_branch }
+ let(:build) { create(:ci_build, commit: ci_commit, ref: ref, name: 'build', stage: 'build') }
+ let(:test) { create(:ci_build, commit: ci_commit, ref: ref, name: 'test', stage: 'test') }
+
+ before do
+ # This behavior of MergeRequest: we instantiate a new object
+ allow_any_instance_of(MergeRequest).to receive(:ci_commit).and_wrap_original do
+ Ci::Commit.find(ci_commit.id)
+ end
+
+ # We create test after the build
+ allow(ci_commit).to receive(:create_next_builds).and_wrap_original do
+ test
+ end
+ end
+
+ it "doesn't merge if some stages failed" do
+ expect(MergeWorker).to_not receive(:perform_async)
+ build.success
+ test.drop
+ end
+
+ it 'merge when all stages succeeded' do
+ expect(MergeWorker).to receive(:perform_async)
+ build.success
+ test.success
+ end
end
end
diff --git a/spec/services/merge_requests/refresh_service_spec.rb b/spec/services/merge_requests/refresh_service_spec.rb
index 450250ba032..fea8182bd30 100644
--- a/spec/services/merge_requests/refresh_service_spec.rb
+++ b/spec/services/merge_requests/refresh_service_spec.rb
@@ -79,7 +79,7 @@ describe MergeRequests::RefreshService, services: true do
it { expect(@merge_request.notes.last.note).to include('changed to merged') }
it { expect(@merge_request).to be_merged }
- it { expect(@merge_request.diffs.length).to be > 0 }
+ it { expect(@merge_request.diffs.size).to be > 0 }
it { expect(@fork_merge_request).to be_merged }
it { expect(@fork_merge_request.notes.last.note).to include('changed to merged') }
end
diff --git a/spec/services/merge_requests/update_service_spec.rb b/spec/services/merge_requests/update_service_spec.rb
index 2e9e6e0870d..99703c7a8ec 100644
--- a/spec/services/merge_requests/update_service_spec.rb
+++ b/spec/services/merge_requests/update_service_spec.rb
@@ -98,6 +98,84 @@ describe MergeRequests::UpdateService, services: true do
end
end
+ context 'todos' do
+ let!(:pending_todo) { create(:todo, :assigned, user: user, project: project, target: merge_request, author: user2) }
+
+ context 'when the title change' do
+ before do
+ update_merge_request({ title: 'New title' })
+ end
+
+ it 'marks pending todos as done' do
+ expect(pending_todo.reload).to be_done
+ end
+ end
+
+ context 'when the description change' do
+ before do
+ update_merge_request({ description: 'Also please fix' })
+ end
+
+ it 'marks pending todos as done' do
+ expect(pending_todo.reload).to be_done
+ end
+ end
+
+ context 'when is reassigned' do
+ before do
+ update_merge_request({ assignee: user2 })
+ end
+
+ it 'marks previous assignee pending todos as done' do
+ expect(pending_todo.reload).to be_done
+ end
+
+ it 'creates a pending todo for new assignee' do
+ attributes = {
+ project: project,
+ author: user,
+ user: user2,
+ target_id: merge_request.id,
+ target_type: merge_request.class.name,
+ action: Todo::ASSIGNED,
+ state: :pending
+ }
+
+ expect(Todo.where(attributes).count).to eq 1
+ end
+ end
+
+ context 'when the milestone change' do
+ before do
+ update_merge_request({ milestone: create(:milestone) })
+ end
+
+ it 'marks pending todos as done' do
+ expect(pending_todo.reload).to be_done
+ end
+ end
+
+ context 'when the labels change' do
+ before do
+ update_merge_request({ label_ids: [label.id] })
+ end
+
+ it 'marks pending todos as done' do
+ expect(pending_todo.reload).to be_done
+ end
+ end
+
+ context 'when the target branch change' do
+ before do
+ update_merge_request({ target_branch: 'target' })
+ end
+
+ it 'marks pending todos as done' do
+ expect(pending_todo.reload).to be_done
+ end
+ end
+ end
+
context 'when MergeRequest has tasks' do
before { update_merge_request({ description: "- [ ] Task 1\n- [ ] Task 2" }) }
@@ -130,6 +208,5 @@ describe MergeRequests::UpdateService, services: true do
end
end
end
-
end
end
diff --git a/spec/services/notes/post_process_service_spec.rb b/spec/services/notes/post_process_service_spec.rb
index 1a3f339bd64..d4c50f824c1 100644
--- a/spec/services/notes/post_process_service_spec.rb
+++ b/spec/services/notes/post_process_service_spec.rb
@@ -20,6 +20,7 @@ describe Notes::PostProcessService, services: true do
it do
expect(project).to receive(:execute_hooks)
expect(project).to receive(:execute_services)
+
Notes::PostProcessService.new(@note).execute
end
end
diff --git a/spec/services/notes/update_service_spec.rb b/spec/services/notes/update_service_spec.rb
new file mode 100644
index 00000000000..dde4bde7dc2
--- /dev/null
+++ b/spec/services/notes/update_service_spec.rb
@@ -0,0 +1,45 @@
+require 'spec_helper'
+
+describe Notes::UpdateService, services: true do
+ let(:project) { create(:empty_project) }
+ let(:user) { create(:user) }
+ let(:user2) { create(:user) }
+ let(:issue) { create(:issue, project: project) }
+ let(:note) { create(:note, project: project, noteable: issue, author: user, note: 'Old note') }
+
+ before do
+ project.team << [user, :master]
+ project.team << [user2, :developer]
+ end
+
+ describe '#execute' do
+ def update_note(opts)
+ @note = Notes::UpdateService.new(project, user, opts).execute(note)
+ @note.reload
+ end
+
+ context 'todos' do
+ let!(:todo) { create(:todo, :assigned, user: user, project: project, target: issue, author: user2) }
+
+ context 'when the note change' do
+ before do
+ update_note({ note: 'New note' })
+ end
+
+ it 'marks todos as done' do
+ expect(todo.reload).to be_done
+ end
+ end
+
+ context 'when the note does not change' do
+ before do
+ update_note({ note: 'Old note' })
+ end
+
+ it 'keep todos' do
+ expect(todo.reload).to be_pending
+ end
+ end
+ end
+ end
+end
diff --git a/spec/services/system_note_service_spec.rb b/spec/services/system_note_service_spec.rb
index d3364a71022..5dcc39f5fdc 100644
--- a/spec/services/system_note_service_spec.rb
+++ b/spec/services/system_note_service_spec.rb
@@ -424,6 +424,21 @@ describe SystemNoteService, services: true do
to be_falsey
end
end
+
+ context 'commit with cross-reference from fork' do
+ let(:author2) { create(:user) }
+ let(:forked_project) { Projects::ForkService.new(project, author2).execute }
+ let(:commit2) { forked_project.commit }
+
+ before do
+ described_class.cross_reference(noteable, commit0, author2)
+ end
+
+ it 'is true when a fork mentions an external issue' do
+ expect(described_class.cross_reference_exists?(noteable, commit2)).
+ to be true
+ end
+ end
end
include JiraServiceHelper
@@ -459,8 +474,8 @@ describe SystemNoteService, services: true do
describe "existing reference" do
before do
- message = "[#{author.name}|http://localhost/u/#{author.username}] mentioned this issue in [a commit of #{project.path_with_namespace}|http://localhost/#{project.path_with_namespace}/commit/#{commit.id}]."
- WebMock.stub_request(:get, jira_api_comment_url).to_return(body: "{\"comments\":[{\"body\":\"#{message}\"}]}")
+ message = %Q{[#{author.name}|http://localhost/u/#{author.username}] mentioned this issue in [a commit of #{project.path_with_namespace}|http://localhost/#{project.path_with_namespace}/commit/#{commit.id}]:\\n'#{commit.title}'}
+ WebMock.stub_request(:get, jira_api_comment_url).to_return(body: %Q({"comments":[{"body":"#{message}"}]}))
end
subject { described_class.cross_reference(jira_issue, commit, author) }
diff --git a/spec/services/todo_service_spec.rb b/spec/services/todo_service_spec.rb
new file mode 100644
index 00000000000..96420acb31d
--- /dev/null
+++ b/spec/services/todo_service_spec.rb
@@ -0,0 +1,274 @@
+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(:project) { create(:project) }
+ let(:mentions) { [author.to_reference, john_doe.to_reference, michael.to_reference, stranger.to_reference].join(' ') }
+ let(:service) { described_class.new }
+
+ before do
+ project.team << [author, :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) }
+
+ describe '#new_issue' do
+ it 'creates a todo if assigned' do
+ service.new_issue(issue, author)
+
+ should_create_todo(user: john_doe, target: issue, action: Todo::ASSIGNED)
+ end
+
+ it 'does not create a todo if unassigned' do
+ should_not_create_any_todo { service.new_issue(unassigned_issue, author) }
+ end
+
+ it 'does not create a todo if assignee is the current user' do
+ should_not_create_any_todo { service.new_issue(unassigned_issue, john_doe) }
+ end
+
+ 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_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)
+ end
+ end
+
+ describe '#update_issue' 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: 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)
+ end
+
+ it 'does not create a todo if user was already mentioned' do
+ create(:todo, :mentioned, user: michael, project: project, target: issue, author: author)
+
+ expect { service.update_issue(issue, author) }.not_to change(michael.todos, :count)
+ end
+ end
+
+ describe '#close_issue' do
+ it 'marks related pending todos to the target for the user as done' do
+ first_todo = create(:todo, :assigned, user: john_doe, project: project, target: issue, author: author)
+ second_todo = create(:todo, :assigned, user: john_doe, project: project, target: issue, author: author)
+
+ service.close_issue(issue, john_doe)
+
+ expect(first_todo.reload).to be_done
+ expect(second_todo.reload).to be_done
+ end
+ end
+
+ describe '#reassigned_issue' do
+ it 'creates a pending todo for new assignee' do
+ unassigned_issue.update_attribute(:assignee, john_doe)
+ service.reassigned_issue(unassigned_issue, author)
+
+ should_create_todo(user: john_doe, target: unassigned_issue, action: Todo::ASSIGNED)
+ end
+
+ it 'does not create a todo if unassigned' do
+ issue.update_attribute(:assignee, nil)
+
+ should_not_create_any_todo { service.reassigned_issue(issue, author) }
+ end
+
+ it 'does not create a todo if new assignee is the current user' do
+ unassigned_issue.update_attribute(:assignee, john_doe)
+
+ should_not_create_any_todo { service.reassigned_issue(unassigned_issue, john_doe) }
+ end
+ end
+
+ describe '#mark_pending_todos_as_done' do
+ it 'marks related pending todos to the target for the user as done' do
+ first_todo = create(:todo, :assigned, user: john_doe, project: project, target: issue, author: author)
+ second_todo = create(:todo, :assigned, user: john_doe, project: project, target: issue, author: author)
+
+ service.mark_pending_todos_as_done(issue, john_doe)
+
+ expect(first_todo.reload).to be_done
+ expect(second_todo.reload).to be_done
+ end
+ end
+
+ 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(: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_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) }
+
+ it 'mark related pending todos to the noteable for the note author as done' do
+ first_todo = create(:todo, :assigned, user: john_doe, project: project, target: issue, author: author)
+ second_todo = create(:todo, :assigned, user: john_doe, project: project, target: issue, author: author)
+
+ service.new_note(note, john_doe)
+
+ expect(first_todo.reload).to be_done
+ expect(second_todo.reload).to be_done
+ end
+
+ it 'mark related pending todos to the noteable for the award note author as done' do
+ service.new_note(award_note, john_doe)
+
+ expect(first_todo.reload).to be_done
+ expect(second_todo.reload).to be_done
+ end
+
+ it 'does not mark related pending todos it is a system note' do
+ service.new_note(system_note, john_doe)
+
+ expect(first_todo.reload).to be_pending
+ expect(second_todo.reload).to be_pending
+ end
+
+ 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: 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)
+ end
+
+ it 'does not create todo when leaving a note on commit' do
+ should_not_create_any_todo { service.new_note(note_on_commit, john_doe) }
+ end
+
+ it 'does not create todo when leaving a note on snippet' do
+ should_not_create_any_todo { service.new_note(note_on_project_snippet, john_doe) }
+ end
+ end
+ end
+
+ describe 'Merge Requests' do
+ let(:mr_assigned) { create(:merge_request, source_project: project, author: author, assignee: john_doe, description: mentions) }
+ let(:mr_unassigned) { create(:merge_request, source_project: project, author: author, assignee: nil) }
+
+ describe '#new_merge_request' do
+ it 'creates a pending todo if assigned' do
+ service.new_merge_request(mr_assigned, author)
+
+ should_create_todo(user: john_doe, target: mr_assigned, action: Todo::ASSIGNED)
+ end
+
+ it 'does not create a todo if unassigned' do
+ should_not_create_any_todo { service.new_merge_request(mr_unassigned, author) }
+ end
+
+ it 'does not create a todo if assignee is the current user' do
+ should_not_create_any_todo { service.new_merge_request(mr_unassigned, john_doe) }
+ end
+
+ 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_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)
+ end
+ end
+
+ describe '#update_merge_request' 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: 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)
+ 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)
+
+ expect { service.update_merge_request(mr_assigned, author) }.not_to change(michael.todos, :count)
+ end
+ end
+
+ describe '#close_merge_request' do
+ it 'marks related pending todos to the target for the user as done' do
+ first_todo = create(:todo, :assigned, user: john_doe, project: project, target: mr_assigned, author: author)
+ second_todo = create(:todo, :assigned, user: john_doe, project: project, target: mr_assigned, author: author)
+ service.close_merge_request(mr_assigned, john_doe)
+
+ expect(first_todo.reload).to be_done
+ expect(second_todo.reload).to be_done
+ end
+ end
+
+ describe '#reassigned_merge_request' do
+ it 'creates a pending todo for new assignee' do
+ mr_unassigned.update_attribute(:assignee, john_doe)
+ service.reassigned_merge_request(mr_unassigned, author)
+
+ should_create_todo(user: john_doe, target: mr_unassigned, action: Todo::ASSIGNED)
+ end
+
+ it 'does not create a todo if unassigned' do
+ mr_assigned.update_attribute(:assignee, nil)
+
+ should_not_create_any_todo { service.reassigned_merge_request(mr_assigned, author) }
+ end
+
+ it 'does not create a todo if new assignee is the current user' do
+ mr_assigned.update_attribute(:assignee, john_doe)
+
+ should_not_create_any_todo { service.reassigned_merge_request(mr_assigned, john_doe) }
+ end
+ end
+
+ describe '#merge_merge_request' do
+ it 'marks related pending todos to the target for the user as done' do
+ first_todo = create(:todo, :assigned, user: john_doe, project: project, target: mr_assigned, author: author)
+ second_todo = create(:todo, :assigned, user: john_doe, project: project, target: mr_assigned, author: author)
+ service.merge_merge_request(mr_assigned, john_doe)
+
+ expect(first_todo.reload).to be_done
+ expect(second_todo.reload).to be_done
+ end
+ end
+ end
+
+ def should_create_todo(attributes = {})
+ attributes.reverse_merge!(
+ project: project,
+ author: author,
+ state: :pending
+ )
+
+ expect(Todo.where(attributes).count).to eq 1
+ end
+
+ def should_not_create_todo(attributes = {})
+ attributes.reverse_merge!(
+ project: project,
+ author: author,
+ state: :pending
+ )
+
+ expect(Todo.where(attributes).count).to eq 0
+ end
+
+ def should_not_create_any_todo
+ expect { yield }.not_to change(Todo, :count)
+ end
+end
diff --git a/spec/support/capybara.rb b/spec/support/capybara.rb
index a698f484df1..65d59e6813c 100644
--- a/spec/support/capybara.rb
+++ b/spec/support/capybara.rb
@@ -7,7 +7,7 @@ timeout = (ENV['CI'] || ENV['CI_SERVER']) ? 90 : 10
Capybara.javascript_driver = :poltergeist
Capybara.register_driver :poltergeist do |app|
- Capybara::Poltergeist::Driver.new(app, js_errors: true, timeout: timeout)
+ Capybara::Poltergeist::Driver.new(app, js_errors: true, timeout: timeout, window_size: [1366, 768])
end
Capybara.default_wait_time = timeout
diff --git a/spec/support/project_hook_data_shared_example.rb b/spec/support/project_hook_data_shared_example.rb
new file mode 100644
index 00000000000..422083875d7
--- /dev/null
+++ b/spec/support/project_hook_data_shared_example.rb
@@ -0,0 +1,27 @@
+RSpec.shared_examples 'project hook data' do |project_key: :project|
+ it 'contains project data' do
+ expect(data[project_key][:name]).to eq(project.name)
+ expect(data[project_key][:description]).to eq(project.description)
+ expect(data[project_key][:web_url]).to eq(project.web_url)
+ expect(data[project_key][:avatar_url]).to eq(project.avatar_url)
+ expect(data[project_key][:git_http_url]).to eq(project.http_url_to_repo)
+ expect(data[project_key][:git_ssh_url]).to eq(project.ssh_url_to_repo)
+ expect(data[project_key][:namespace]).to eq(project.namespace.name)
+ expect(data[project_key][:visibility_level]).to eq(project.visibility_level)
+ expect(data[project_key][:path_with_namespace]).to eq(project.path_with_namespace)
+ expect(data[project_key][:default_branch]).to eq(project.default_branch)
+ expect(data[project_key][:homepage]).to eq(project.web_url)
+ expect(data[project_key][:url]).to eq(project.url_to_repo)
+ expect(data[project_key][:ssh_url]).to eq(project.ssh_url_to_repo)
+ expect(data[project_key][:http_url]).to eq(project.http_url_to_repo)
+ end
+end
+
+RSpec.shared_examples 'deprecated repository hook data' do |project_key: :project|
+ it 'contains deprecated repository data' do
+ expect(data[:repository][:name]).to eq(project.name)
+ expect(data[:repository][:description]).to eq(project.description)
+ expect(data[:repository][:url]).to eq(project.url_to_repo)
+ expect(data[:repository][:homepage]).to eq(project.web_url)
+ end
+end
diff --git a/spec/support/workhorse_helpers.rb b/spec/support/workhorse_helpers.rb
new file mode 100644
index 00000000000..107b6e30924
--- /dev/null
+++ b/spec/support/workhorse_helpers.rb
@@ -0,0 +1,16 @@
+module WorkhorseHelpers
+ extend self
+
+ def workhorse_send_data
+ @_workhorse_send_data ||= begin
+ header = response.headers[Gitlab::Workhorse::SEND_DATA_HEADER]
+ split_header = header.split(':')
+ type = split_header.shift
+ header = split_header.join(':')
+ [
+ type,
+ JSON.parse(Base64.urlsafe_decode64(header)),
+ ]
+ end
+ end
+end
diff --git a/spec/views/devise/shared/_signin_box.html.haml_spec.rb b/spec/views/devise/shared/_signin_box.html.haml_spec.rb
new file mode 100644
index 00000000000..05a76ee4bdb
--- /dev/null
+++ b/spec/views/devise/shared/_signin_box.html.haml_spec.rb
@@ -0,0 +1,37 @@
+require 'rails_helper'
+
+describe 'devise/shared/_signin_box' do
+ describe 'Crowd form' do
+ before do
+ stub_devise
+ assign(:ldap_servers, [])
+ end
+
+ it 'is shown when Crowd is enabled' do
+ enable_crowd
+
+ render
+
+ expect(rendered).to have_selector('#tab-crowd form')
+ end
+
+ it 'is not shown when Crowd is disabled' do
+ render
+
+ expect(rendered).not_to have_selector('#tab-crowd')
+ end
+ end
+
+ def stub_devise
+ allow(view).to receive(:devise_mapping).and_return(Devise.mappings[:user])
+ allow(view).to receive(:resource).and_return(spy)
+ allow(view).to receive(:resource_name).and_return(:user)
+ end
+
+ def enable_crowd
+ allow(view).to receive(:form_based_providers).and_return([:crowd])
+ allow(view).to receive(:crowd_enabled?).and_return(true)
+ allow(view).to receive(:user_omniauth_authorize_path).with('crowd').
+ and_return('/crowd')
+ end
+end
diff --git a/spec/workers/repository_fork_worker_spec.rb b/spec/workers/repository_fork_worker_spec.rb
index dae31992620..172537474ee 100644
--- a/spec/workers/repository_fork_worker_spec.rb
+++ b/spec/workers/repository_fork_worker_spec.rb
@@ -19,6 +19,18 @@ describe RepositoryForkWorker do
fork_project.namespace.path)
end
+ it 'flushes the empty caches' do
+ expect_any_instance_of(Gitlab::Shell).to receive(:fork_repository).
+ with(project.path_with_namespace, fork_project.namespace.path).
+ and_return(true)
+
+ expect_any_instance_of(Repository).to receive(:expire_emptiness_caches).
+ and_call_original
+
+ subject.perform(project.id, project.path_with_namespace,
+ fork_project.namespace.path)
+ end
+
it "handles bad fork" do
expect_any_instance_of(Gitlab::Shell).to receive(:fork_repository).and_return(false)
subject.perform(
diff --git a/spec/workers/repository_import_worker_spec.rb b/spec/workers/repository_import_worker_spec.rb
new file mode 100644
index 00000000000..6739063543b
--- /dev/null
+++ b/spec/workers/repository_import_worker_spec.rb
@@ -0,0 +1,19 @@
+require 'spec_helper'
+
+describe RepositoryImportWorker do
+ let(:project) { create(:project) }
+
+ subject { described_class.new }
+
+ describe '#perform' do
+ it 'imports a project' do
+ expect_any_instance_of(Projects::ImportService).to receive(:execute).
+ and_return({ status: :ok })
+
+ expect_any_instance_of(Repository).to receive(:expire_emptiness_caches)
+ expect_any_instance_of(Project).to receive(:import_finish)
+
+ subject.perform(project.id)
+ end
+ end
+end
diff --git a/vendor/assets/javascripts/cropper.js b/vendor/assets/javascripts/cropper.js
new file mode 100755
index 00000000000..84aa6119ec3
--- /dev/null
+++ b/vendor/assets/javascripts/cropper.js
@@ -0,0 +1,2972 @@
+/*!
+ * Cropper v2.2.5
+ * https://github.com/fengyuanchen/cropper
+ *
+ * Copyright (c) 2014-2016 Fengyuan Chen and contributors
+ * Released under the MIT license
+ *
+ * Date: 2016-01-18T05:42:50.800Z
+ */
+
+(function (factory) {
+ if (typeof define === 'function' && define.amd) {
+ // AMD. Register as anonymous module.
+ define(['jquery'], factory);
+ } else if (typeof exports === 'object') {
+ // Node / CommonJS
+ factory(require('jquery'));
+ } else {
+ // Browser globals.
+ factory(jQuery);
+ }
+})(function ($) {
+
+ 'use strict';
+
+ // Globals
+ var $window = $(window);
+ var $document = $(document);
+ var location = window.location;
+ var ArrayBuffer = window.ArrayBuffer;
+ var Uint8Array = window.Uint8Array;
+ var DataView = window.DataView;
+ var btoa = window.btoa;
+
+ // Constants
+ var NAMESPACE = 'cropper';
+
+ // Classes
+ var CLASS_MODAL = 'cropper-modal';
+ var CLASS_HIDE = 'cropper-hide';
+ var CLASS_HIDDEN = 'cropper-hidden';
+ var CLASS_INVISIBLE = 'cropper-invisible';
+ var CLASS_MOVE = 'cropper-move';
+ var CLASS_CROP = 'cropper-crop';
+ var CLASS_DISABLED = 'cropper-disabled';
+ var CLASS_BG = 'cropper-bg';
+
+ // Events
+ var EVENT_MOUSE_DOWN = 'mousedown touchstart pointerdown MSPointerDown';
+ var EVENT_MOUSE_MOVE = 'mousemove touchmove pointermove MSPointerMove';
+ var EVENT_MOUSE_UP = 'mouseup touchend touchcancel pointerup pointercancel MSPointerUp MSPointerCancel';
+ var EVENT_WHEEL = 'wheel mousewheel DOMMouseScroll';
+ var EVENT_DBLCLICK = 'dblclick';
+ var EVENT_LOAD = 'load.' + NAMESPACE;
+ var EVENT_ERROR = 'error.' + NAMESPACE;
+ var EVENT_RESIZE = 'resize.' + NAMESPACE; // Bind to window with namespace
+ var EVENT_BUILD = 'build.' + NAMESPACE;
+ var EVENT_BUILT = 'built.' + NAMESPACE;
+ var EVENT_CROP_START = 'cropstart.' + NAMESPACE;
+ var EVENT_CROP_MOVE = 'cropmove.' + NAMESPACE;
+ var EVENT_CROP_END = 'cropend.' + NAMESPACE;
+ var EVENT_CROP = 'crop.' + NAMESPACE;
+ var EVENT_ZOOM = 'zoom.' + NAMESPACE;
+
+ // RegExps
+ var REGEXP_ACTIONS = /e|w|s|n|se|sw|ne|nw|all|crop|move|zoom/;
+ var REGEXP_DATA_URL = /^data\:/;
+ var REGEXP_DATA_URL_HEAD = /^data\:([^\;]+)\;base64,/;
+ var REGEXP_DATA_URL_JPEG = /^data\:image\/jpeg.*;base64,/;
+
+ // Data keys
+ var DATA_PREVIEW = 'preview';
+ var DATA_ACTION = 'action';
+
+ // Actions
+ var ACTION_EAST = 'e';
+ var ACTION_WEST = 'w';
+ var ACTION_SOUTH = 's';
+ var ACTION_NORTH = 'n';
+ var ACTION_SOUTH_EAST = 'se';
+ var ACTION_SOUTH_WEST = 'sw';
+ var ACTION_NORTH_EAST = 'ne';
+ var ACTION_NORTH_WEST = 'nw';
+ var ACTION_ALL = 'all';
+ var ACTION_CROP = 'crop';
+ var ACTION_MOVE = 'move';
+ var ACTION_ZOOM = 'zoom';
+ var ACTION_NONE = 'none';
+
+ // Supports
+ var SUPPORT_CANVAS = $.isFunction($('<canvas>')[0].getContext);
+
+ // Maths
+ var num = Number;
+ var min = Math.min;
+ var max = Math.max;
+ var abs = Math.abs;
+ var sin = Math.sin;
+ var cos = Math.cos;
+ var sqrt = Math.sqrt;
+ var round = Math.round;
+ var floor = Math.floor;
+
+ // Utilities
+ var fromCharCode = String.fromCharCode;
+
+ function isNumber(n) {
+ return typeof n === 'number' && !isNaN(n);
+ }
+
+ function isUndefined(n) {
+ return typeof n === 'undefined';
+ }
+
+ function toArray(obj, offset) {
+ var args = [];
+
+ // This is necessary for IE8
+ if (isNumber(offset)) {
+ args.push(offset);
+ }
+
+ return args.slice.apply(obj, args);
+ }
+
+ // Custom proxy to avoid jQuery's guid
+ function proxy(fn, context) {
+ var args = toArray(arguments, 2);
+
+ return function () {
+ return fn.apply(context, args.concat(toArray(arguments)));
+ };
+ }
+
+ function isCrossOriginURL(url) {
+ var parts = url.match(/^(https?:)\/\/([^\:\/\?#]+):?(\d*)/i);
+
+ return parts && (
+ parts[1] !== location.protocol ||
+ parts[2] !== location.hostname ||
+ parts[3] !== location.port
+ );
+ }
+
+ function addTimestamp(url) {
+ var timestamp = 'timestamp=' + (new Date()).getTime();
+
+ return (url + (url.indexOf('?') === -1 ? '?' : '&') + timestamp);
+ }
+
+ function getCrossOrigin(crossOrigin) {
+ return crossOrigin ? ' crossOrigin="' + crossOrigin + '"' : '';
+ }
+
+ function getImageSize(image, callback) {
+ var newImage;
+
+ // Modern browsers
+ if (image.naturalWidth) {
+ return callback(image.naturalWidth, image.naturalHeight);
+ }
+
+ // IE8: Don't use `new Image()` here (#319)
+ newImage = document.createElement('img');
+
+ newImage.onload = function () {
+ callback(this.width, this.height);
+ };
+
+ newImage.src = image.src;
+ }
+
+ function getTransform(options) {
+ var transforms = [];
+ var rotate = options.rotate;
+ var scaleX = options.scaleX;
+ var scaleY = options.scaleY;
+
+ if (isNumber(rotate)) {
+ transforms.push('rotate(' + rotate + 'deg)');
+ }
+
+ if (isNumber(scaleX) && isNumber(scaleY)) {
+ transforms.push('scale(' + scaleX + ',' + scaleY + ')');
+ }
+
+ return transforms.length ? transforms.join(' ') : 'none';
+ }
+
+ function getRotatedSizes(data, isReversed) {
+ var deg = abs(data.degree) % 180;
+ var arc = (deg > 90 ? (180 - deg) : deg) * Math.PI / 180;
+ var sinArc = sin(arc);
+ var cosArc = cos(arc);
+ var width = data.width;
+ var height = data.height;
+ var aspectRatio = data.aspectRatio;
+ var newWidth;
+ var newHeight;
+
+ if (!isReversed) {
+ newWidth = width * cosArc + height * sinArc;
+ newHeight = width * sinArc + height * cosArc;
+ } else {
+ newWidth = width / (cosArc + sinArc / aspectRatio);
+ newHeight = newWidth / aspectRatio;
+ }
+
+ return {
+ width: newWidth,
+ height: newHeight
+ };
+ }
+
+ function getSourceCanvas(image, data) {
+ var canvas = $('<canvas>')[0];
+ var context = canvas.getContext('2d');
+ var x = 0;
+ var y = 0;
+ var width = data.naturalWidth;
+ var height = data.naturalHeight;
+ var rotate = data.rotate;
+ var scaleX = data.scaleX;
+ var scaleY = data.scaleY;
+ var scalable = isNumber(scaleX) && isNumber(scaleY) && (scaleX !== 1 || scaleY !== 1);
+ var rotatable = isNumber(rotate) && rotate !== 0;
+ var advanced = rotatable || scalable;
+ var canvasWidth = width;
+ var canvasHeight = height;
+ var translateX;
+ var translateY;
+ var rotated;
+
+ if (scalable) {
+ translateX = width / 2;
+ translateY = height / 2;
+ }
+
+ if (rotatable) {
+ rotated = getRotatedSizes({
+ width: width,
+ height: height,
+ degree: rotate
+ });
+
+ canvasWidth = rotated.width;
+ canvasHeight = rotated.height;
+ translateX = rotated.width / 2;
+ translateY = rotated.height / 2;
+ }
+
+ canvas.width = canvasWidth;
+ canvas.height = canvasHeight;
+
+ if (advanced) {
+ x = -width / 2;
+ y = -height / 2;
+
+ context.save();
+ context.translate(translateX, translateY);
+ }
+
+ if (rotatable) {
+ context.rotate(rotate * Math.PI / 180);
+ }
+
+ // Should call `scale` after rotated
+ if (scalable) {
+ context.scale(scaleX, scaleY);
+ }
+
+ context.drawImage(image, floor(x), floor(y), floor(width), floor(height));
+
+ if (advanced) {
+ context.restore();
+ }
+
+ return canvas;
+ }
+
+ function getTouchesCenter(touches) {
+ var length = touches.length;
+ var pageX = 0;
+ var pageY = 0;
+
+ if (length) {
+ $.each(touches, function (i, touch) {
+ pageX += touch.pageX;
+ pageY += touch.pageY;
+ });
+
+ pageX /= length;
+ pageY /= length;
+ }
+
+ return {
+ pageX: pageX,
+ pageY: pageY
+ };
+ }
+
+ function getStringFromCharCode(dataView, start, length) {
+ var str = '';
+ var i;
+
+ for (i = start, length += start; i < length; i++) {
+ str += fromCharCode(dataView.getUint8(i));
+ }
+
+ return str;
+ }
+
+ function getOrientation(arrayBuffer) {
+ var dataView = new DataView(arrayBuffer);
+ var length = dataView.byteLength;
+ var orientation;
+ var exifIDCode;
+ var tiffOffset;
+ var firstIFDOffset;
+ var littleEndian;
+ var endianness;
+ var app1Start;
+ var ifdStart;
+ var offset;
+ var i;
+
+ // Only handle JPEG image (start by 0xFFD8)
+ if (dataView.getUint8(0) === 0xFF && dataView.getUint8(1) === 0xD8) {
+ offset = 2;
+
+ while (offset < length) {
+ if (dataView.getUint8(offset) === 0xFF && dataView.getUint8(offset + 1) === 0xE1) {
+ app1Start = offset;
+ break;
+ }
+
+ offset++;
+ }
+ }
+
+ if (app1Start) {
+ exifIDCode = app1Start + 4;
+ tiffOffset = app1Start + 10;
+
+ if (getStringFromCharCode(dataView, exifIDCode, 4) === 'Exif') {
+ endianness = dataView.getUint16(tiffOffset);
+ littleEndian = endianness === 0x4949;
+
+ if (littleEndian || endianness === 0x4D4D /* bigEndian */) {
+ if (dataView.getUint16(tiffOffset + 2, littleEndian) === 0x002A) {
+ firstIFDOffset = dataView.getUint32(tiffOffset + 4, littleEndian);
+
+ if (firstIFDOffset >= 0x00000008) {
+ ifdStart = tiffOffset + firstIFDOffset;
+ }
+ }
+ }
+ }
+ }
+
+ if (ifdStart) {
+ length = dataView.getUint16(ifdStart, littleEndian);
+
+ for (i = 0; i < length; i++) {
+ offset = ifdStart + i * 12 + 2;
+
+ if (dataView.getUint16(offset, littleEndian) === 0x0112 /* Orientation */) {
+
+ // 8 is the offset of the current tag's value
+ offset += 8;
+
+ // Get the original orientation value
+ orientation = dataView.getUint16(offset, littleEndian);
+
+ // Override the orientation with the default value: 1
+ dataView.setUint16(offset, 1, littleEndian);
+ break;
+ }
+ }
+ }
+
+ return orientation;
+ }
+
+ function dataURLToArrayBuffer(dataURL) {
+ var base64 = dataURL.replace(REGEXP_DATA_URL_HEAD, '');
+ var binary = atob(base64);
+ var length = binary.length;
+ var arrayBuffer = new ArrayBuffer(length);
+ var dataView = new Uint8Array(arrayBuffer);
+ var i;
+
+ for (i = 0; i < length; i++) {
+ dataView[i] = binary.charCodeAt(i);
+ }
+
+ return arrayBuffer;
+ }
+
+ // Only available for JPEG image
+ function arrayBufferToDataURL(arrayBuffer) {
+ var dataView = new Uint8Array(arrayBuffer);
+ var length = dataView.length;
+ var base64 = '';
+ var i;
+
+ for (i = 0; i < length; i++) {
+ base64 += fromCharCode(dataView[i]);
+ }
+
+ return 'data:image/jpeg;base64,' + btoa(base64);
+ }
+
+ function Cropper(element, options) {
+ this.$element = $(element);
+ this.options = $.extend({}, Cropper.DEFAULTS, $.isPlainObject(options) && options);
+ this.isLoaded = false;
+ this.isBuilt = false;
+ this.isCompleted = false;
+ this.isRotated = false;
+ this.isCropped = false;
+ this.isDisabled = false;
+ this.isReplaced = false;
+ this.isLimited = false;
+ this.wheeling = false;
+ this.isImg = false;
+ this.originalUrl = '';
+ this.canvas = null;
+ this.cropBox = null;
+ this.init();
+ }
+
+ Cropper.prototype = {
+ constructor: Cropper,
+
+ init: function () {
+ var $this = this.$element;
+ var url;
+
+ if ($this.is('img')) {
+ this.isImg = true;
+
+ // Should use `$.fn.attr` here. e.g.: "img/picture.jpg"
+ this.originalUrl = url = $this.attr('src');
+
+ // Stop when it's a blank image
+ if (!url) {
+ return;
+ }
+
+ // Should use `$.fn.prop` here. e.g.: "http://example.com/img/picture.jpg"
+ url = $this.prop('src');
+ } else if ($this.is('canvas') && SUPPORT_CANVAS) {
+ url = $this[0].toDataURL();
+ }
+
+ this.load(url);
+ },
+
+ // A shortcut for triggering custom events
+ trigger: function (type, data) {
+ var e = $.Event(type, data);
+
+ this.$element.trigger(e);
+
+ return e;
+ },
+
+ load: function (url) {
+ var options = this.options;
+ var $this = this.$element;
+ var read;
+ var xhr;
+
+ if (!url) {
+ return;
+ }
+
+ // Trigger build event first
+ $this.one(EVENT_BUILD, options.build);
+
+ if (this.trigger(EVENT_BUILD).isDefaultPrevented()) {
+ return;
+ }
+
+ this.url = url;
+ this.image = {};
+
+ if (!options.checkOrientation || !ArrayBuffer) {
+ return this.clone();
+ }
+
+ read = $.proxy(this.read, this);
+
+ // XMLHttpRequest disallows to open a Data URL in some browsers like IE11 and Safari
+ if (REGEXP_DATA_URL.test(url)) {
+ return REGEXP_DATA_URL_JPEG.test(url) ?
+ read(dataURLToArrayBuffer(url)) :
+ this.clone();
+ }
+
+ xhr = new XMLHttpRequest();
+
+ xhr.onerror = xhr.onabort = $.proxy(function () {
+ this.clone();
+ }, this);
+
+ xhr.onload = function () {
+ read(this.response);
+ };
+
+ xhr.open('get', url);
+ xhr.responseType = 'arraybuffer';
+ xhr.send();
+ },
+
+ read: function (arrayBuffer) {
+ var options = this.options;
+ var orientation = getOrientation(arrayBuffer);
+ var image = this.image;
+ var rotate;
+ var scaleX;
+ var scaleY;
+
+ if (orientation > 1) {
+ this.url = arrayBufferToDataURL(arrayBuffer);
+
+ switch (orientation) {
+
+ // flip horizontal
+ case 2:
+ scaleX = -1;
+ break;
+
+ // rotate left 180°
+ case 3:
+ rotate = -180;
+ break;
+
+ // flip vertical
+ case 4:
+ scaleY = -1;
+ break;
+
+ // flip vertical + rotate right 90°
+ case 5:
+ rotate = 90;
+ scaleY = -1;
+ break;
+
+ // rotate right 90°
+ case 6:
+ rotate = 90;
+ break;
+
+ // flip horizontal + rotate right 90°
+ case 7:
+ rotate = 90;
+ scaleX = -1;
+ break;
+
+ // rotate left 90°
+ case 8:
+ rotate = -90;
+ break;
+ }
+ }
+
+ if (options.rotatable) {
+ image.rotate = rotate;
+ }
+
+ if (options.scalable) {
+ image.scaleX = scaleX;
+ image.scaleY = scaleY;
+ }
+
+ this.clone();
+ },
+
+ clone: function () {
+ var options = this.options;
+ var $this = this.$element;
+ var url = this.url;
+ var crossOrigin = '';
+ var crossOriginUrl;
+ var $clone;
+
+ if (options.checkCrossOrigin && isCrossOriginURL(url)) {
+ crossOrigin = $this.prop('crossOrigin');
+
+ if (crossOrigin) {
+ crossOriginUrl = url;
+ } else {
+ crossOrigin = 'anonymous';
+
+ // Bust cache (#148) when there is not a "crossOrigin" property
+ crossOriginUrl = addTimestamp(url);
+ }
+ }
+
+ this.crossOrigin = crossOrigin;
+ this.crossOriginUrl = crossOriginUrl;
+ this.$clone = $clone = $('<img' + getCrossOrigin(crossOrigin) + ' src="' + (crossOriginUrl || url) + '">');
+
+ if (this.isImg) {
+ if ($this[0].complete) {
+ this.start();
+ } else {
+ $this.one(EVENT_LOAD, $.proxy(this.start, this));
+ }
+ } else {
+ $clone.
+ one(EVENT_LOAD, $.proxy(this.start, this)).
+ one(EVENT_ERROR, $.proxy(this.stop, this)).
+ addClass(CLASS_HIDE).
+ insertAfter($this);
+ }
+ },
+
+ start: function () {
+ var $image = this.$element;
+ var $clone = this.$clone;
+
+ if (!this.isImg) {
+ $clone.off(EVENT_ERROR, this.stop);
+ $image = $clone;
+ }
+
+ getImageSize($image[0], $.proxy(function (naturalWidth, naturalHeight) {
+ $.extend(this.image, {
+ naturalWidth: naturalWidth,
+ naturalHeight: naturalHeight,
+ aspectRatio: naturalWidth / naturalHeight
+ });
+
+ this.isLoaded = true;
+ this.build();
+ }, this));
+ },
+
+ stop: function () {
+ this.$clone.remove();
+ this.$clone = null;
+ },
+
+ build: function () {
+ var options = this.options;
+ var $this = this.$element;
+ var $clone = this.$clone;
+ var $cropper;
+ var $cropBox;
+ var $face;
+
+ if (!this.isLoaded) {
+ return;
+ }
+
+ // Unbuild first when replace
+ if (this.isBuilt) {
+ this.unbuild();
+ }
+
+ // Create cropper elements
+ this.$container = $this.parent();
+ this.$cropper = $cropper = $(Cropper.TEMPLATE);
+ this.$canvas = $cropper.find('.cropper-canvas').append($clone);
+ this.$dragBox = $cropper.find('.cropper-drag-box');
+ this.$cropBox = $cropBox = $cropper.find('.cropper-crop-box');
+ this.$viewBox = $cropper.find('.cropper-view-box');
+ this.$face = $face = $cropBox.find('.cropper-face');
+
+ // Hide the original image
+ $this.addClass(CLASS_HIDDEN).after($cropper);
+
+ // Show the clone image if is hidden
+ if (!this.isImg) {
+ $clone.removeClass(CLASS_HIDE);
+ }
+
+ this.initPreview();
+ this.bind();
+
+ options.aspectRatio = max(0, options.aspectRatio) || NaN;
+ options.viewMode = max(0, min(3, round(options.viewMode))) || 0;
+
+ if (options.autoCrop) {
+ this.isCropped = true;
+
+ if (options.modal) {
+ this.$dragBox.addClass(CLASS_MODAL);
+ }
+ } else {
+ $cropBox.addClass(CLASS_HIDDEN);
+ }
+
+ if (!options.guides) {
+ $cropBox.find('.cropper-dashed').addClass(CLASS_HIDDEN);
+ }
+
+ if (!options.center) {
+ $cropBox.find('.cropper-center').addClass(CLASS_HIDDEN);
+ }
+
+ if (options.cropBoxMovable) {
+ $face.addClass(CLASS_MOVE).data(DATA_ACTION, ACTION_ALL);
+ }
+
+ if (!options.highlight) {
+ $face.addClass(CLASS_INVISIBLE);
+ }
+
+ if (options.background) {
+ $cropper.addClass(CLASS_BG);
+ }
+
+ if (!options.cropBoxResizable) {
+ $cropBox.find('.cropper-line, .cropper-point').addClass(CLASS_HIDDEN);
+ }
+
+ this.setDragMode(options.dragMode);
+ this.render();
+ this.isBuilt = true;
+ this.setData(options.data);
+ $this.one(EVENT_BUILT, options.built);
+
+ // Trigger the built event asynchronously to keep `data('cropper')` is defined
+ setTimeout($.proxy(function () {
+ this.trigger(EVENT_BUILT);
+ this.isCompleted = true;
+ }, this), 0);
+ },
+
+ unbuild: function () {
+ if (!this.isBuilt) {
+ return;
+ }
+
+ this.isBuilt = false;
+ this.isCompleted = false;
+ this.initialImage = null;
+
+ // Clear `initialCanvas` is necessary when replace
+ this.initialCanvas = null;
+ this.initialCropBox = null;
+ this.container = null;
+ this.canvas = null;
+
+ // Clear `cropBox` is necessary when replace
+ this.cropBox = null;
+ this.unbind();
+
+ this.resetPreview();
+ this.$preview = null;
+
+ this.$viewBox = null;
+ this.$cropBox = null;
+ this.$dragBox = null;
+ this.$canvas = null;
+ this.$container = null;
+
+ this.$cropper.remove();
+ this.$cropper = null;
+ },
+
+ render: function () {
+ this.initContainer();
+ this.initCanvas();
+ this.initCropBox();
+
+ this.renderCanvas();
+
+ if (this.isCropped) {
+ this.renderCropBox();
+ }
+ },
+
+ initContainer: function () {
+ var options = this.options;
+ var $this = this.$element;
+ var $container = this.$container;
+ var $cropper = this.$cropper;
+
+ $cropper.addClass(CLASS_HIDDEN);
+ $this.removeClass(CLASS_HIDDEN);
+
+ $cropper.css((this.container = {
+ width: max($container.width(), num(options.minContainerWidth) || 200),
+ height: max($container.height(), num(options.minContainerHeight) || 100)
+ }));
+
+ $this.addClass(CLASS_HIDDEN);
+ $cropper.removeClass(CLASS_HIDDEN);
+ },
+
+ // Canvas (image wrapper)
+ initCanvas: function () {
+ var viewMode = this.options.viewMode;
+ var container = this.container;
+ var containerWidth = container.width;
+ var containerHeight = container.height;
+ var image = this.image;
+ var imageNaturalWidth = image.naturalWidth;
+ var imageNaturalHeight = image.naturalHeight;
+ var is90Degree = abs(image.rotate) === 90;
+ var naturalWidth = is90Degree ? imageNaturalHeight : imageNaturalWidth;
+ var naturalHeight = is90Degree ? imageNaturalWidth : imageNaturalHeight;
+ var aspectRatio = naturalWidth / naturalHeight;
+ var canvasWidth = containerWidth;
+ var canvasHeight = containerHeight;
+ var canvas;
+
+ if (containerHeight * aspectRatio > containerWidth) {
+ if (viewMode === 3) {
+ canvasWidth = containerHeight * aspectRatio;
+ } else {
+ canvasHeight = containerWidth / aspectRatio;
+ }
+ } else {
+ if (viewMode === 3) {
+ canvasHeight = containerWidth / aspectRatio;
+ } else {
+ canvasWidth = containerHeight * aspectRatio;
+ }
+ }
+
+ canvas = {
+ naturalWidth: naturalWidth,
+ naturalHeight: naturalHeight,
+ aspectRatio: aspectRatio,
+ width: canvasWidth,
+ height: canvasHeight
+ };
+
+ canvas.oldLeft = canvas.left = (containerWidth - canvasWidth) / 2;
+ canvas.oldTop = canvas.top = (containerHeight - canvasHeight) / 2;
+
+ this.canvas = canvas;
+ this.isLimited = (viewMode === 1 || viewMode === 2);
+ this.limitCanvas(true, true);
+ this.initialImage = $.extend({}, image);
+ this.initialCanvas = $.extend({}, canvas);
+ },
+
+ limitCanvas: function (isSizeLimited, isPositionLimited) {
+ var options = this.options;
+ var viewMode = options.viewMode;
+ var container = this.container;
+ var containerWidth = container.width;
+ var containerHeight = container.height;
+ var canvas = this.canvas;
+ var aspectRatio = canvas.aspectRatio;
+ var cropBox = this.cropBox;
+ var isCropped = this.isCropped && cropBox;
+ var minCanvasWidth;
+ var minCanvasHeight;
+ var newCanvasLeft;
+ var newCanvasTop;
+
+ if (isSizeLimited) {
+ minCanvasWidth = num(options.minCanvasWidth) || 0;
+ minCanvasHeight = num(options.minCanvasHeight) || 0;
+
+ if (viewMode) {
+ if (viewMode > 1) {
+ minCanvasWidth = max(minCanvasWidth, containerWidth);
+ minCanvasHeight = max(minCanvasHeight, containerHeight);
+
+ if (viewMode === 3) {
+ if (minCanvasHeight * aspectRatio > minCanvasWidth) {
+ minCanvasWidth = minCanvasHeight * aspectRatio;
+ } else {
+ minCanvasHeight = minCanvasWidth / aspectRatio;
+ }
+ }
+ } else {
+ if (minCanvasWidth) {
+ minCanvasWidth = max(minCanvasWidth, isCropped ? cropBox.width : 0);
+ } else if (minCanvasHeight) {
+ minCanvasHeight = max(minCanvasHeight, isCropped ? cropBox.height : 0);
+ } else if (isCropped) {
+ minCanvasWidth = cropBox.width;
+ minCanvasHeight = cropBox.height;
+
+ if (minCanvasHeight * aspectRatio > minCanvasWidth) {
+ minCanvasWidth = minCanvasHeight * aspectRatio;
+ } else {
+ minCanvasHeight = minCanvasWidth / aspectRatio;
+ }
+ }
+ }
+ }
+
+ if (minCanvasWidth && minCanvasHeight) {
+ if (minCanvasHeight * aspectRatio > minCanvasWidth) {
+ minCanvasHeight = minCanvasWidth / aspectRatio;
+ } else {
+ minCanvasWidth = minCanvasHeight * aspectRatio;
+ }
+ } else if (minCanvasWidth) {
+ minCanvasHeight = minCanvasWidth / aspectRatio;
+ } else if (minCanvasHeight) {
+ minCanvasWidth = minCanvasHeight * aspectRatio;
+ }
+
+ canvas.minWidth = minCanvasWidth;
+ canvas.minHeight = minCanvasHeight;
+ canvas.maxWidth = Infinity;
+ canvas.maxHeight = Infinity;
+ }
+
+ if (isPositionLimited) {
+ if (viewMode) {
+ newCanvasLeft = containerWidth - canvas.width;
+ newCanvasTop = containerHeight - canvas.height;
+
+ canvas.minLeft = min(0, newCanvasLeft);
+ canvas.minTop = min(0, newCanvasTop);
+ canvas.maxLeft = max(0, newCanvasLeft);
+ canvas.maxTop = max(0, newCanvasTop);
+
+ if (isCropped && this.isLimited) {
+ canvas.minLeft = min(
+ cropBox.left,
+ cropBox.left + cropBox.width - canvas.width
+ );
+ canvas.minTop = min(
+ cropBox.top,
+ cropBox.top + cropBox.height - canvas.height
+ );
+ canvas.maxLeft = cropBox.left;
+ canvas.maxTop = cropBox.top;
+
+ if (viewMode === 2) {
+ if (canvas.width >= containerWidth) {
+ canvas.minLeft = min(0, newCanvasLeft);
+ canvas.maxLeft = max(0, newCanvasLeft);
+ }
+
+ if (canvas.height >= containerHeight) {
+ canvas.minTop = min(0, newCanvasTop);
+ canvas.maxTop = max(0, newCanvasTop);
+ }
+ }
+ }
+ } else {
+ canvas.minLeft = -canvas.width;
+ canvas.minTop = -canvas.height;
+ canvas.maxLeft = containerWidth;
+ canvas.maxTop = containerHeight;
+ }
+ }
+ },
+
+ renderCanvas: function (isChanged) {
+ var canvas = this.canvas;
+ var image = this.image;
+ var rotate = image.rotate;
+ var naturalWidth = image.naturalWidth;
+ var naturalHeight = image.naturalHeight;
+ var aspectRatio;
+ var rotated;
+
+ if (this.isRotated) {
+ this.isRotated = false;
+
+ // Computes rotated sizes with image sizes
+ rotated = getRotatedSizes({
+ width: image.width,
+ height: image.height,
+ degree: rotate
+ });
+
+ aspectRatio = rotated.width / rotated.height;
+
+ if (aspectRatio !== canvas.aspectRatio) {
+ canvas.left -= (rotated.width - canvas.width) / 2;
+ canvas.top -= (rotated.height - canvas.height) / 2;
+ canvas.width = rotated.width;
+ canvas.height = rotated.height;
+ canvas.aspectRatio = aspectRatio;
+ canvas.naturalWidth = naturalWidth;
+ canvas.naturalHeight = naturalHeight;
+
+ // Computes rotated sizes with natural image sizes
+ if (rotate % 180) {
+ rotated = getRotatedSizes({
+ width: naturalWidth,
+ height: naturalHeight,
+ degree: rotate
+ });
+
+ canvas.naturalWidth = rotated.width;
+ canvas.naturalHeight = rotated.height;
+ }
+
+ this.limitCanvas(true, false);
+ }
+ }
+
+ if (canvas.width > canvas.maxWidth || canvas.width < canvas.minWidth) {
+ canvas.left = canvas.oldLeft;
+ }
+
+ if (canvas.height > canvas.maxHeight || canvas.height < canvas.minHeight) {
+ canvas.top = canvas.oldTop;
+ }
+
+ canvas.width = min(max(canvas.width, canvas.minWidth), canvas.maxWidth);
+ canvas.height = min(max(canvas.height, canvas.minHeight), canvas.maxHeight);
+
+ this.limitCanvas(false, true);
+
+ canvas.oldLeft = canvas.left = min(max(canvas.left, canvas.minLeft), canvas.maxLeft);
+ canvas.oldTop = canvas.top = min(max(canvas.top, canvas.minTop), canvas.maxTop);
+
+ this.$canvas.css({
+ width: canvas.width,
+ height: canvas.height,
+ left: canvas.left,
+ top: canvas.top
+ });
+
+ this.renderImage();
+
+ if (this.isCropped && this.isLimited) {
+ this.limitCropBox(true, true);
+ }
+
+ if (isChanged) {
+ this.output();
+ }
+ },
+
+ renderImage: function (isChanged) {
+ var canvas = this.canvas;
+ var image = this.image;
+ var reversed;
+
+ if (image.rotate) {
+ reversed = getRotatedSizes({
+ width: canvas.width,
+ height: canvas.height,
+ degree: image.rotate,
+ aspectRatio: image.aspectRatio
+ }, true);
+ }
+
+ $.extend(image, reversed ? {
+ width: reversed.width,
+ height: reversed.height,
+ left: (canvas.width - reversed.width) / 2,
+ top: (canvas.height - reversed.height) / 2
+ } : {
+ width: canvas.width,
+ height: canvas.height,
+ left: 0,
+ top: 0
+ });
+
+ this.$clone.css({
+ width: image.width,
+ height: image.height,
+ marginLeft: image.left,
+ marginTop: image.top,
+ transform: getTransform(image)
+ });
+
+ if (isChanged) {
+ this.output();
+ }
+ },
+
+ initCropBox: function () {
+ var options = this.options;
+ var canvas = this.canvas;
+ var aspectRatio = options.aspectRatio;
+ var autoCropArea = num(options.autoCropArea) || 0.8;
+ var cropBox = {
+ width: canvas.width,
+ height: canvas.height
+ };
+
+ if (aspectRatio) {
+ if (canvas.height * aspectRatio > canvas.width) {
+ cropBox.height = cropBox.width / aspectRatio;
+ } else {
+ cropBox.width = cropBox.height * aspectRatio;
+ }
+ }
+
+ this.cropBox = cropBox;
+ this.limitCropBox(true, true);
+
+ // Initialize auto crop area
+ cropBox.width = min(max(cropBox.width, cropBox.minWidth), cropBox.maxWidth);
+ cropBox.height = min(max(cropBox.height, cropBox.minHeight), cropBox.maxHeight);
+
+ // The width of auto crop area must large than "minWidth", and the height too. (#164)
+ cropBox.width = max(cropBox.minWidth, cropBox.width * autoCropArea);
+ cropBox.height = max(cropBox.minHeight, cropBox.height * autoCropArea);
+ cropBox.oldLeft = cropBox.left = canvas.left + (canvas.width - cropBox.width) / 2;
+ cropBox.oldTop = cropBox.top = canvas.top + (canvas.height - cropBox.height) / 2;
+
+ this.initialCropBox = $.extend({}, cropBox);
+ },
+
+ limitCropBox: function (isSizeLimited, isPositionLimited) {
+ var options = this.options;
+ var aspectRatio = options.aspectRatio;
+ var container = this.container;
+ var containerWidth = container.width;
+ var containerHeight = container.height;
+ var canvas = this.canvas;
+ var cropBox = this.cropBox;
+ var isLimited = this.isLimited;
+ var minCropBoxWidth;
+ var minCropBoxHeight;
+ var maxCropBoxWidth;
+ var maxCropBoxHeight;
+
+ if (isSizeLimited) {
+ minCropBoxWidth = num(options.minCropBoxWidth) || 0;
+ minCropBoxHeight = num(options.minCropBoxHeight) || 0;
+
+ // The min/maxCropBoxWidth/Height must be less than containerWidth/Height
+ minCropBoxWidth = min(minCropBoxWidth, containerWidth);
+ minCropBoxHeight = min(minCropBoxHeight, containerHeight);
+ maxCropBoxWidth = min(containerWidth, isLimited ? canvas.width : containerWidth);
+ maxCropBoxHeight = min(containerHeight, isLimited ? canvas.height : containerHeight);
+
+ if (aspectRatio) {
+ if (minCropBoxWidth && minCropBoxHeight) {
+ if (minCropBoxHeight * aspectRatio > minCropBoxWidth) {
+ minCropBoxHeight = minCropBoxWidth / aspectRatio;
+ } else {
+ minCropBoxWidth = minCropBoxHeight * aspectRatio;
+ }
+ } else if (minCropBoxWidth) {
+ minCropBoxHeight = minCropBoxWidth / aspectRatio;
+ } else if (minCropBoxHeight) {
+ minCropBoxWidth = minCropBoxHeight * aspectRatio;
+ }
+
+ if (maxCropBoxHeight * aspectRatio > maxCropBoxWidth) {
+ maxCropBoxHeight = maxCropBoxWidth / aspectRatio;
+ } else {
+ maxCropBoxWidth = maxCropBoxHeight * aspectRatio;
+ }
+ }
+
+ // The minWidth/Height must be less than maxWidth/Height
+ cropBox.minWidth = min(minCropBoxWidth, maxCropBoxWidth);
+ cropBox.minHeight = min(minCropBoxHeight, maxCropBoxHeight);
+ cropBox.maxWidth = maxCropBoxWidth;
+ cropBox.maxHeight = maxCropBoxHeight;
+ }
+
+ if (isPositionLimited) {
+ if (isLimited) {
+ cropBox.minLeft = max(0, canvas.left);
+ cropBox.minTop = max(0, canvas.top);
+ cropBox.maxLeft = min(containerWidth, canvas.left + canvas.width) - cropBox.width;
+ cropBox.maxTop = min(containerHeight, canvas.top + canvas.height) - cropBox.height;
+ } else {
+ cropBox.minLeft = 0;
+ cropBox.minTop = 0;
+ cropBox.maxLeft = containerWidth - cropBox.width;
+ cropBox.maxTop = containerHeight - cropBox.height;
+ }
+ }
+ },
+
+ renderCropBox: function () {
+ var options = this.options;
+ var container = this.container;
+ var containerWidth = container.width;
+ var containerHeight = container.height;
+ var cropBox = this.cropBox;
+
+ if (cropBox.width > cropBox.maxWidth || cropBox.width < cropBox.minWidth) {
+ cropBox.left = cropBox.oldLeft;
+ }
+
+ if (cropBox.height > cropBox.maxHeight || cropBox.height < cropBox.minHeight) {
+ cropBox.top = cropBox.oldTop;
+ }
+
+ cropBox.width = min(max(cropBox.width, cropBox.minWidth), cropBox.maxWidth);
+ cropBox.height = min(max(cropBox.height, cropBox.minHeight), cropBox.maxHeight);
+
+ this.limitCropBox(false, true);
+
+ cropBox.oldLeft = cropBox.left = min(max(cropBox.left, cropBox.minLeft), cropBox.maxLeft);
+ cropBox.oldTop = cropBox.top = min(max(cropBox.top, cropBox.minTop), cropBox.maxTop);
+
+ if (options.movable && options.cropBoxMovable) {
+
+ // Turn to move the canvas when the crop box is equal to the container
+ this.$face.data(DATA_ACTION, (cropBox.width === containerWidth && cropBox.height === containerHeight) ? ACTION_MOVE : ACTION_ALL);
+ }
+
+ this.$cropBox.css({
+ width: cropBox.width,
+ height: cropBox.height,
+ left: cropBox.left,
+ top: cropBox.top
+ });
+
+ if (this.isCropped && this.isLimited) {
+ this.limitCanvas(true, true);
+ }
+
+ if (!this.isDisabled) {
+ this.output();
+ }
+ },
+
+ output: function () {
+ this.preview();
+
+ if (this.isCompleted) {
+ this.trigger(EVENT_CROP, this.getData());
+ } else if (!this.isBuilt) {
+
+ // Only trigger one crop event before complete
+ this.$element.one(EVENT_BUILT, $.proxy(function () {
+ this.trigger(EVENT_CROP, this.getData());
+ }, this));
+ }
+ },
+
+ initPreview: function () {
+ var crossOrigin = getCrossOrigin(this.crossOrigin);
+ var url = crossOrigin ? this.crossOriginUrl : this.url;
+
+ this.$preview = $(this.options.preview);
+ this.$viewBox.html('<img' + crossOrigin + ' src="' + url + '">');
+ this.$preview.each(function () {
+ var $this = $(this);
+
+ // Save the original size for recover
+ $this.data(DATA_PREVIEW, {
+ width: $this.width(),
+ height: $this.height(),
+ html: $this.html()
+ });
+
+ /**
+ * Override img element styles
+ * Add `display:block` to avoid margin top issue
+ * (Occur only when margin-top <= -height)
+ */
+ $this.html(
+ '<img' + crossOrigin + ' src="' + url + '" style="' +
+ 'display:block;width:100%;height:auto;' +
+ 'min-width:0!important;min-height:0!important;' +
+ 'max-width:none!important;max-height:none!important;' +
+ 'image-orientation:0deg!important;">'
+ );
+ });
+ },
+
+ resetPreview: function () {
+ this.$preview.each(function () {
+ var $this = $(this);
+ var data = $this.data(DATA_PREVIEW);
+
+ $this.css({
+ width: data.width,
+ height: data.height
+ }).html(data.html).removeData(DATA_PREVIEW);
+ });
+ },
+
+ preview: function () {
+ var image = this.image;
+ var canvas = this.canvas;
+ var cropBox = this.cropBox;
+ var cropBoxWidth = cropBox.width;
+ var cropBoxHeight = cropBox.height;
+ var width = image.width;
+ var height = image.height;
+ var left = cropBox.left - canvas.left - image.left;
+ var top = cropBox.top - canvas.top - image.top;
+
+ if (!this.isCropped || this.isDisabled) {
+ return;
+ }
+
+ this.$viewBox.find('img').css({
+ width: width,
+ height: height,
+ marginLeft: -left,
+ marginTop: -top,
+ transform: getTransform(image)
+ });
+
+ this.$preview.each(function () {
+ var $this = $(this);
+ var data = $this.data(DATA_PREVIEW);
+ var originalWidth = data.width;
+ var originalHeight = data.height;
+ var newWidth = originalWidth;
+ var newHeight = originalHeight;
+ var ratio = 1;
+
+ if (cropBoxWidth) {
+ ratio = originalWidth / cropBoxWidth;
+ newHeight = cropBoxHeight * ratio;
+ }
+
+ if (cropBoxHeight && newHeight > originalHeight) {
+ ratio = originalHeight / cropBoxHeight;
+ newWidth = cropBoxWidth * ratio;
+ newHeight = originalHeight;
+ }
+
+ $this.css({
+ width: newWidth,
+ height: newHeight
+ }).find('img').css({
+ width: width * ratio,
+ height: height * ratio,
+ marginLeft: -left * ratio,
+ marginTop: -top * ratio,
+ transform: getTransform(image)
+ });
+ });
+ },
+
+ bind: function () {
+ var options = this.options;
+ var $this = this.$element;
+ var $cropper = this.$cropper;
+
+ if ($.isFunction(options.cropstart)) {
+ $this.on(EVENT_CROP_START, options.cropstart);
+ }
+
+ if ($.isFunction(options.cropmove)) {
+ $this.on(EVENT_CROP_MOVE, options.cropmove);
+ }
+
+ if ($.isFunction(options.cropend)) {
+ $this.on(EVENT_CROP_END, options.cropend);
+ }
+
+ if ($.isFunction(options.crop)) {
+ $this.on(EVENT_CROP, options.crop);
+ }
+
+ if ($.isFunction(options.zoom)) {
+ $this.on(EVENT_ZOOM, options.zoom);
+ }
+
+ $cropper.on(EVENT_MOUSE_DOWN, $.proxy(this.cropStart, this));
+
+ if (options.zoomable && options.zoomOnWheel) {
+ $cropper.on(EVENT_WHEEL, $.proxy(this.wheel, this));
+ }
+
+ if (options.toggleDragModeOnDblclick) {
+ $cropper.on(EVENT_DBLCLICK, $.proxy(this.dblclick, this));
+ }
+
+ $document.
+ on(EVENT_MOUSE_MOVE, (this._cropMove = proxy(this.cropMove, this))).
+ on(EVENT_MOUSE_UP, (this._cropEnd = proxy(this.cropEnd, this)));
+
+ if (options.responsive) {
+ $window.on(EVENT_RESIZE, (this._resize = proxy(this.resize, this)));
+ }
+ },
+
+ unbind: function () {
+ var options = this.options;
+ var $this = this.$element;
+ var $cropper = this.$cropper;
+
+ if ($.isFunction(options.cropstart)) {
+ $this.off(EVENT_CROP_START, options.cropstart);
+ }
+
+ if ($.isFunction(options.cropmove)) {
+ $this.off(EVENT_CROP_MOVE, options.cropmove);
+ }
+
+ if ($.isFunction(options.cropend)) {
+ $this.off(EVENT_CROP_END, options.cropend);
+ }
+
+ if ($.isFunction(options.crop)) {
+ $this.off(EVENT_CROP, options.crop);
+ }
+
+ if ($.isFunction(options.zoom)) {
+ $this.off(EVENT_ZOOM, options.zoom);
+ }
+
+ $cropper.off(EVENT_MOUSE_DOWN, this.cropStart);
+
+ if (options.zoomable && options.zoomOnWheel) {
+ $cropper.off(EVENT_WHEEL, this.wheel);
+ }
+
+ if (options.toggleDragModeOnDblclick) {
+ $cropper.off(EVENT_DBLCLICK, this.dblclick);
+ }
+
+ $document.
+ off(EVENT_MOUSE_MOVE, this._cropMove).
+ off(EVENT_MOUSE_UP, this._cropEnd);
+
+ if (options.responsive) {
+ $window.off(EVENT_RESIZE, this._resize);
+ }
+ },
+
+ resize: function () {
+ var restore = this.options.restore;
+ var $container = this.$container;
+ var container = this.container;
+ var canvasData;
+ var cropBoxData;
+ var ratio;
+
+ // Check `container` is necessary for IE8
+ if (this.isDisabled || !container) {
+ return;
+ }
+
+ ratio = $container.width() / container.width;
+
+ // Resize when width changed or height changed
+ if (ratio !== 1 || $container.height() !== container.height) {
+ if (restore) {
+ canvasData = this.getCanvasData();
+ cropBoxData = this.getCropBoxData();
+ }
+
+ this.render();
+
+ if (restore) {
+ this.setCanvasData($.each(canvasData, function (i, n) {
+ canvasData[i] = n * ratio;
+ }));
+ this.setCropBoxData($.each(cropBoxData, function (i, n) {
+ cropBoxData[i] = n * ratio;
+ }));
+ }
+ }
+ },
+
+ dblclick: function () {
+ if (this.isDisabled) {
+ return;
+ }
+
+ if (this.$dragBox.hasClass(CLASS_CROP)) {
+ this.setDragMode(ACTION_MOVE);
+ } else {
+ this.setDragMode(ACTION_CROP);
+ }
+ },
+
+ wheel: function (event) {
+ var e = event.originalEvent || event;
+ var ratio = num(this.options.wheelZoomRatio) || 0.1;
+ var delta = 1;
+
+ if (this.isDisabled) {
+ return;
+ }
+
+ event.preventDefault();
+
+ // Limit wheel speed to prevent zoom too fast
+ if (this.wheeling) {
+ return;
+ }
+
+ this.wheeling = true;
+
+ setTimeout($.proxy(function () {
+ this.wheeling = false;
+ }, this), 50);
+
+ if (e.deltaY) {
+ delta = e.deltaY > 0 ? 1 : -1;
+ } else if (e.wheelDelta) {
+ delta = -e.wheelDelta / 120;
+ } else if (e.detail) {
+ delta = e.detail > 0 ? 1 : -1;
+ }
+
+ this.zoom(-delta * ratio, event);
+ },
+
+ cropStart: function (event) {
+ var options = this.options;
+ var originalEvent = event.originalEvent;
+ var touches = originalEvent && originalEvent.touches;
+ var e = event;
+ var touchesLength;
+ var action;
+
+ if (this.isDisabled) {
+ return;
+ }
+
+ if (touches) {
+ touchesLength = touches.length;
+
+ if (touchesLength > 1) {
+ if (options.zoomable && options.zoomOnTouch && touchesLength === 2) {
+ e = touches[1];
+ this.startX2 = e.pageX;
+ this.startY2 = e.pageY;
+ action = ACTION_ZOOM;
+ } else {
+ return;
+ }
+ }
+
+ e = touches[0];
+ }
+
+ action = action || $(e.target).data(DATA_ACTION);
+
+ if (REGEXP_ACTIONS.test(action)) {
+ if (this.trigger(EVENT_CROP_START, {
+ originalEvent: originalEvent,
+ action: action
+ }).isDefaultPrevented()) {
+ return;
+ }
+
+ event.preventDefault();
+
+ this.action = action;
+ this.cropping = false;
+
+ // IE8 has `event.pageX/Y`, but not `event.originalEvent.pageX/Y`
+ // IE10 has `event.originalEvent.pageX/Y`, but not `event.pageX/Y`
+ this.startX = e.pageX || originalEvent && originalEvent.pageX;
+ this.startY = e.pageY || originalEvent && originalEvent.pageY;
+
+ if (action === ACTION_CROP) {
+ this.cropping = true;
+ this.$dragBox.addClass(CLASS_MODAL);
+ }
+ }
+ },
+
+ cropMove: function (event) {
+ var options = this.options;
+ var originalEvent = event.originalEvent;
+ var touches = originalEvent && originalEvent.touches;
+ var e = event;
+ var action = this.action;
+ var touchesLength;
+
+ if (this.isDisabled) {
+ return;
+ }
+
+ if (touches) {
+ touchesLength = touches.length;
+
+ if (touchesLength > 1) {
+ if (options.zoomable && options.zoomOnTouch && touchesLength === 2) {
+ e = touches[1];
+ this.endX2 = e.pageX;
+ this.endY2 = e.pageY;
+ } else {
+ return;
+ }
+ }
+
+ e = touches[0];
+ }
+
+ if (action) {
+ if (this.trigger(EVENT_CROP_MOVE, {
+ originalEvent: originalEvent,
+ action: action
+ }).isDefaultPrevented()) {
+ return;
+ }
+
+ event.preventDefault();
+
+ this.endX = e.pageX || originalEvent && originalEvent.pageX;
+ this.endY = e.pageY || originalEvent && originalEvent.pageY;
+
+ this.change(e.shiftKey, action === ACTION_ZOOM ? event : null);
+ }
+ },
+
+ cropEnd: function (event) {
+ var originalEvent = event.originalEvent;
+ var action = this.action;
+
+ if (this.isDisabled) {
+ return;
+ }
+
+ if (action) {
+ event.preventDefault();
+
+ if (this.cropping) {
+ this.cropping = false;
+ this.$dragBox.toggleClass(CLASS_MODAL, this.isCropped && this.options.modal);
+ }
+
+ this.action = '';
+
+ this.trigger(EVENT_CROP_END, {
+ originalEvent: originalEvent,
+ action: action
+ });
+ }
+ },
+
+ change: function (shiftKey, event) {
+ var options = this.options;
+ var aspectRatio = options.aspectRatio;
+ var action = this.action;
+ var container = this.container;
+ var canvas = this.canvas;
+ var cropBox = this.cropBox;
+ var width = cropBox.width;
+ var height = cropBox.height;
+ var left = cropBox.left;
+ var top = cropBox.top;
+ var right = left + width;
+ var bottom = top + height;
+ var minLeft = 0;
+ var minTop = 0;
+ var maxWidth = container.width;
+ var maxHeight = container.height;
+ var renderable = true;
+ var offset;
+ var range;
+
+ // Locking aspect ratio in "free mode" by holding shift key (#259)
+ if (!aspectRatio && shiftKey) {
+ aspectRatio = width && height ? width / height : 1;
+ }
+
+ if (this.limited) {
+ minLeft = cropBox.minLeft;
+ minTop = cropBox.minTop;
+ maxWidth = minLeft + min(container.width, canvas.width);
+ maxHeight = minTop + min(container.height, canvas.height);
+ }
+
+ range = {
+ x: this.endX - this.startX,
+ y: this.endY - this.startY
+ };
+
+ if (aspectRatio) {
+ range.X = range.y * aspectRatio;
+ range.Y = range.x / aspectRatio;
+ }
+
+ switch (action) {
+ // Move crop box
+ case ACTION_ALL:
+ left += range.x;
+ top += range.y;
+ break;
+
+ // Resize crop box
+ case ACTION_EAST:
+ if (range.x >= 0 && (right >= maxWidth || aspectRatio &&
+ (top <= minTop || bottom >= maxHeight))) {
+
+ renderable = false;
+ break;
+ }
+
+ width += range.x;
+
+ if (aspectRatio) {
+ height = width / aspectRatio;
+ top -= range.Y / 2;
+ }
+
+ if (width < 0) {
+ action = ACTION_WEST;
+ width = 0;
+ }
+
+ break;
+
+ case ACTION_NORTH:
+ if (range.y <= 0 && (top <= minTop || aspectRatio &&
+ (left <= minLeft || right >= maxWidth))) {
+
+ renderable = false;
+ break;
+ }
+
+ height -= range.y;
+ top += range.y;
+
+ if (aspectRatio) {
+ width = height * aspectRatio;
+ left += range.X / 2;
+ }
+
+ if (height < 0) {
+ action = ACTION_SOUTH;
+ height = 0;
+ }
+
+ break;
+
+ case ACTION_WEST:
+ if (range.x <= 0 && (left <= minLeft || aspectRatio &&
+ (top <= minTop || bottom >= maxHeight))) {
+
+ renderable = false;
+ break;
+ }
+
+ width -= range.x;
+ left += range.x;
+
+ if (aspectRatio) {
+ height = width / aspectRatio;
+ top += range.Y / 2;
+ }
+
+ if (width < 0) {
+ action = ACTION_EAST;
+ width = 0;
+ }
+
+ break;
+
+ case ACTION_SOUTH:
+ if (range.y >= 0 && (bottom >= maxHeight || aspectRatio &&
+ (left <= minLeft || right >= maxWidth))) {
+
+ renderable = false;
+ break;
+ }
+
+ height += range.y;
+
+ if (aspectRatio) {
+ width = height * aspectRatio;
+ left -= range.X / 2;
+ }
+
+ if (height < 0) {
+ action = ACTION_NORTH;
+ height = 0;
+ }
+
+ break;
+
+ case ACTION_NORTH_EAST:
+ if (aspectRatio) {
+ if (range.y <= 0 && (top <= minTop || right >= maxWidth)) {
+ renderable = false;
+ break;
+ }
+
+ height -= range.y;
+ top += range.y;
+ width = height * aspectRatio;
+ } else {
+ if (range.x >= 0) {
+ if (right < maxWidth) {
+ width += range.x;
+ } else if (range.y <= 0 && top <= minTop) {
+ renderable = false;
+ }
+ } else {
+ width += range.x;
+ }
+
+ if (range.y <= 0) {
+ if (top > minTop) {
+ height -= range.y;
+ top += range.y;
+ }
+ } else {
+ height -= range.y;
+ top += range.y;
+ }
+ }
+
+ if (width < 0 && height < 0) {
+ action = ACTION_SOUTH_WEST;
+ height = 0;
+ width = 0;
+ } else if (width < 0) {
+ action = ACTION_NORTH_WEST;
+ width = 0;
+ } else if (height < 0) {
+ action = ACTION_SOUTH_EAST;
+ height = 0;
+ }
+
+ break;
+
+ case ACTION_NORTH_WEST:
+ if (aspectRatio) {
+ if (range.y <= 0 && (top <= minTop || left <= minLeft)) {
+ renderable = false;
+ break;
+ }
+
+ height -= range.y;
+ top += range.y;
+ width = height * aspectRatio;
+ left += range.X;
+ } else {
+ if (range.x <= 0) {
+ if (left > minLeft) {
+ width -= range.x;
+ left += range.x;
+ } else if (range.y <= 0 && top <= minTop) {
+ renderable = false;
+ }
+ } else {
+ width -= range.x;
+ left += range.x;
+ }
+
+ if (range.y <= 0) {
+ if (top > minTop) {
+ height -= range.y;
+ top += range.y;
+ }
+ } else {
+ height -= range.y;
+ top += range.y;
+ }
+ }
+
+ if (width < 0 && height < 0) {
+ action = ACTION_SOUTH_EAST;
+ height = 0;
+ width = 0;
+ } else if (width < 0) {
+ action = ACTION_NORTH_EAST;
+ width = 0;
+ } else if (height < 0) {
+ action = ACTION_SOUTH_WEST;
+ height = 0;
+ }
+
+ break;
+
+ case ACTION_SOUTH_WEST:
+ if (aspectRatio) {
+ if (range.x <= 0 && (left <= minLeft || bottom >= maxHeight)) {
+ renderable = false;
+ break;
+ }
+
+ width -= range.x;
+ left += range.x;
+ height = width / aspectRatio;
+ } else {
+ if (range.x <= 0) {
+ if (left > minLeft) {
+ width -= range.x;
+ left += range.x;
+ } else if (range.y >= 0 && bottom >= maxHeight) {
+ renderable = false;
+ }
+ } else {
+ width -= range.x;
+ left += range.x;
+ }
+
+ if (range.y >= 0) {
+ if (bottom < maxHeight) {
+ height += range.y;
+ }
+ } else {
+ height += range.y;
+ }
+ }
+
+ if (width < 0 && height < 0) {
+ action = ACTION_NORTH_EAST;
+ height = 0;
+ width = 0;
+ } else if (width < 0) {
+ action = ACTION_SOUTH_EAST;
+ width = 0;
+ } else if (height < 0) {
+ action = ACTION_NORTH_WEST;
+ height = 0;
+ }
+
+ break;
+
+ case ACTION_SOUTH_EAST:
+ if (aspectRatio) {
+ if (range.x >= 0 && (right >= maxWidth || bottom >= maxHeight)) {
+ renderable = false;
+ break;
+ }
+
+ width += range.x;
+ height = width / aspectRatio;
+ } else {
+ if (range.x >= 0) {
+ if (right < maxWidth) {
+ width += range.x;
+ } else if (range.y >= 0 && bottom >= maxHeight) {
+ renderable = false;
+ }
+ } else {
+ width += range.x;
+ }
+
+ if (range.y >= 0) {
+ if (bottom < maxHeight) {
+ height += range.y;
+ }
+ } else {
+ height += range.y;
+ }
+ }
+
+ if (width < 0 && height < 0) {
+ action = ACTION_NORTH_WEST;
+ height = 0;
+ width = 0;
+ } else if (width < 0) {
+ action = ACTION_SOUTH_WEST;
+ width = 0;
+ } else if (height < 0) {
+ action = ACTION_NORTH_EAST;
+ height = 0;
+ }
+
+ break;
+
+ // Move canvas
+ case ACTION_MOVE:
+ this.move(range.x, range.y);
+ renderable = false;
+ break;
+
+ // Zoom canvas
+ case ACTION_ZOOM:
+ this.zoom((function (x1, y1, x2, y2) {
+ var z1 = sqrt(x1 * x1 + y1 * y1);
+ var z2 = sqrt(x2 * x2 + y2 * y2);
+
+ return (z2 - z1) / z1;
+ })(
+ abs(this.startX - this.startX2),
+ abs(this.startY - this.startY2),
+ abs(this.endX - this.endX2),
+ abs(this.endY - this.endY2)
+ ), event);
+ this.startX2 = this.endX2;
+ this.startY2 = this.endY2;
+ renderable = false;
+ break;
+
+ // Create crop box
+ case ACTION_CROP:
+ if (!range.x || !range.y) {
+ renderable = false;
+ break;
+ }
+
+ offset = this.$cropper.offset();
+ left = this.startX - offset.left;
+ top = this.startY - offset.top;
+ width = cropBox.minWidth;
+ height = cropBox.minHeight;
+
+ if (range.x > 0) {
+ action = range.y > 0 ? ACTION_SOUTH_EAST : ACTION_NORTH_EAST;
+ } else if (range.x < 0) {
+ left -= width;
+ action = range.y > 0 ? ACTION_SOUTH_WEST : ACTION_NORTH_WEST;
+ }
+
+ if (range.y < 0) {
+ top -= height;
+ }
+
+ // Show the crop box if is hidden
+ if (!this.isCropped) {
+ this.$cropBox.removeClass(CLASS_HIDDEN);
+ this.isCropped = true;
+
+ if (this.limited) {
+ this.limitCropBox(true, true);
+ }
+ }
+
+ break;
+
+ // No default
+ }
+
+ if (renderable) {
+ cropBox.width = width;
+ cropBox.height = height;
+ cropBox.left = left;
+ cropBox.top = top;
+ this.action = action;
+
+ this.renderCropBox();
+ }
+
+ // Override
+ this.startX = this.endX;
+ this.startY = this.endY;
+ },
+
+ // Show the crop box manually
+ crop: function () {
+ if (!this.isBuilt || this.isDisabled) {
+ return;
+ }
+
+ if (!this.isCropped) {
+ this.isCropped = true;
+ this.limitCropBox(true, true);
+
+ if (this.options.modal) {
+ this.$dragBox.addClass(CLASS_MODAL);
+ }
+
+ this.$cropBox.removeClass(CLASS_HIDDEN);
+ }
+
+ this.setCropBoxData(this.initialCropBox);
+ },
+
+ // Reset the image and crop box to their initial states
+ reset: function () {
+ if (!this.isBuilt || this.isDisabled) {
+ return;
+ }
+
+ this.image = $.extend({}, this.initialImage);
+ this.canvas = $.extend({}, this.initialCanvas);
+ this.cropBox = $.extend({}, this.initialCropBox);
+
+ this.renderCanvas();
+
+ if (this.isCropped) {
+ this.renderCropBox();
+ }
+ },
+
+ // Clear the crop box
+ clear: function () {
+ if (!this.isCropped || this.isDisabled) {
+ return;
+ }
+
+ $.extend(this.cropBox, {
+ left: 0,
+ top: 0,
+ width: 0,
+ height: 0
+ });
+
+ this.isCropped = false;
+ this.renderCropBox();
+
+ this.limitCanvas(true, true);
+
+ // Render canvas after crop box rendered
+ this.renderCanvas();
+
+ this.$dragBox.removeClass(CLASS_MODAL);
+ this.$cropBox.addClass(CLASS_HIDDEN);
+ },
+
+ /**
+ * Replace the image's src and rebuild the cropper
+ *
+ * @param {String} url
+ */
+ replace: function (url) {
+ if (!this.isDisabled && url) {
+ if (this.isImg) {
+ this.isReplaced = true;
+ this.$element.attr('src', url);
+ }
+
+ // Clear previous data
+ this.options.data = null;
+ this.load(url);
+ }
+ },
+
+ // Enable (unfreeze) the cropper
+ enable: function () {
+ if (this.isBuilt) {
+ this.isDisabled = false;
+ this.$cropper.removeClass(CLASS_DISABLED);
+ }
+ },
+
+ // Disable (freeze) the cropper
+ disable: function () {
+ if (this.isBuilt) {
+ this.isDisabled = true;
+ this.$cropper.addClass(CLASS_DISABLED);
+ }
+ },
+
+ // Destroy the cropper and remove the instance from the image
+ destroy: function () {
+ var $this = this.$element;
+
+ if (this.isLoaded) {
+ if (this.isImg && this.isReplaced) {
+ $this.attr('src', this.originalUrl);
+ }
+
+ this.unbuild();
+ $this.removeClass(CLASS_HIDDEN);
+ } else {
+ if (this.isImg) {
+ $this.off(EVENT_LOAD, this.start);
+ } else if (this.$clone) {
+ this.$clone.remove();
+ }
+ }
+
+ $this.removeData(NAMESPACE);
+ },
+
+ /**
+ * Move the canvas with relative offsets
+ *
+ * @param {Number} offsetX
+ * @param {Number} offsetY (optional)
+ */
+ move: function (offsetX, offsetY) {
+ var canvas = this.canvas;
+
+ this.moveTo(
+ isUndefined(offsetX) ? offsetX : canvas.left + num(offsetX),
+ isUndefined(offsetY) ? offsetY : canvas.top + num(offsetY)
+ );
+ },
+
+ /**
+ * Move the canvas to an absolute point
+ *
+ * @param {Number} x
+ * @param {Number} y (optional)
+ */
+ moveTo: function (x, y) {
+ var canvas = this.canvas;
+ var isChanged = false;
+
+ // If "y" is not present, its default value is "x"
+ if (isUndefined(y)) {
+ y = x;
+ }
+
+ x = num(x);
+ y = num(y);
+
+ if (this.isBuilt && !this.isDisabled && this.options.movable) {
+ if (isNumber(x)) {
+ canvas.left = x;
+ isChanged = true;
+ }
+
+ if (isNumber(y)) {
+ canvas.top = y;
+ isChanged = true;
+ }
+
+ if (isChanged) {
+ this.renderCanvas(true);
+ }
+ }
+ },
+
+ /**
+ * Zoom the canvas with a relative ratio
+ *
+ * @param {Number} ratio
+ * @param {jQuery Event} _event (private)
+ */
+ zoom: function (ratio, _event) {
+ var canvas = this.canvas;
+
+ ratio = num(ratio);
+
+ if (ratio < 0) {
+ ratio = 1 / (1 - ratio);
+ } else {
+ ratio = 1 + ratio;
+ }
+
+ this.zoomTo(canvas.width * ratio / canvas.naturalWidth, _event);
+ },
+
+ /**
+ * Zoom the canvas to an absolute ratio
+ *
+ * @param {Number} ratio
+ * @param {jQuery Event} _event (private)
+ */
+ zoomTo: function (ratio, _event) {
+ var options = this.options;
+ var canvas = this.canvas;
+ var width = canvas.width;
+ var height = canvas.height;
+ var naturalWidth = canvas.naturalWidth;
+ var naturalHeight = canvas.naturalHeight;
+ var originalEvent;
+ var newWidth;
+ var newHeight;
+ var offset;
+ var center;
+
+ ratio = num(ratio);
+
+ if (ratio >= 0 && this.isBuilt && !this.isDisabled && options.zoomable) {
+ newWidth = naturalWidth * ratio;
+ newHeight = naturalHeight * ratio;
+
+ if (_event) {
+ originalEvent = _event.originalEvent;
+ }
+
+ if (this.trigger(EVENT_ZOOM, {
+ originalEvent: originalEvent,
+ oldRatio: width / naturalWidth,
+ ratio: newWidth / naturalWidth
+ }).isDefaultPrevented()) {
+ return;
+ }
+
+ if (originalEvent) {
+ offset = this.$cropper.offset();
+ center = originalEvent.touches ? getTouchesCenter(originalEvent.touches) : {
+ pageX: _event.pageX || originalEvent.pageX || 0,
+ pageY: _event.pageY || originalEvent.pageY || 0
+ };
+
+ // Zoom from the triggering point of the event
+ canvas.left -= (newWidth - width) * (
+ ((center.pageX - offset.left) - canvas.left) / width
+ );
+ canvas.top -= (newHeight - height) * (
+ ((center.pageY - offset.top) - canvas.top) / height
+ );
+ } else {
+
+ // Zoom from the center of the canvas
+ canvas.left -= (newWidth - width) / 2;
+ canvas.top -= (newHeight - height) / 2;
+ }
+
+ canvas.width = newWidth;
+ canvas.height = newHeight;
+ this.renderCanvas(true);
+ }
+ },
+
+ /**
+ * Rotate the canvas with a relative degree
+ *
+ * @param {Number} degree
+ */
+ rotate: function (degree) {
+ this.rotateTo((this.image.rotate || 0) + num(degree));
+ },
+
+ /**
+ * Rotate the canvas to an absolute degree
+ * https://developer.mozilla.org/en-US/docs/Web/CSS/transform-function#rotate()
+ *
+ * @param {Number} degree
+ */
+ rotateTo: function (degree) {
+ degree = num(degree);
+
+ if (isNumber(degree) && this.isBuilt && !this.isDisabled && this.options.rotatable) {
+ this.image.rotate = degree % 360;
+ this.isRotated = true;
+ this.renderCanvas(true);
+ }
+ },
+
+ /**
+ * Scale the image
+ * https://developer.mozilla.org/en-US/docs/Web/CSS/transform-function#scale()
+ *
+ * @param {Number} scaleX
+ * @param {Number} scaleY (optional)
+ */
+ scale: function (scaleX, scaleY) {
+ var image = this.image;
+ var isChanged = false;
+
+ // If "scaleY" is not present, its default value is "scaleX"
+ if (isUndefined(scaleY)) {
+ scaleY = scaleX;
+ }
+
+ scaleX = num(scaleX);
+ scaleY = num(scaleY);
+
+ if (this.isBuilt && !this.isDisabled && this.options.scalable) {
+ if (isNumber(scaleX)) {
+ image.scaleX = scaleX;
+ isChanged = true;
+ }
+
+ if (isNumber(scaleY)) {
+ image.scaleY = scaleY;
+ isChanged = true;
+ }
+
+ if (isChanged) {
+ this.renderImage(true);
+ }
+ }
+ },
+
+ /**
+ * Scale the abscissa of the image
+ *
+ * @param {Number} scaleX
+ */
+ scaleX: function (scaleX) {
+ var scaleY = this.image.scaleY;
+
+ this.scale(scaleX, isNumber(scaleY) ? scaleY : 1);
+ },
+
+ /**
+ * Scale the ordinate of the image
+ *
+ * @param {Number} scaleY
+ */
+ scaleY: function (scaleY) {
+ var scaleX = this.image.scaleX;
+
+ this.scale(isNumber(scaleX) ? scaleX : 1, scaleY);
+ },
+
+ /**
+ * Get the cropped area position and size data (base on the original image)
+ *
+ * @param {Boolean} isRounded (optional)
+ * @return {Object} data
+ */
+ getData: function (isRounded) {
+ var options = this.options;
+ var image = this.image;
+ var canvas = this.canvas;
+ var cropBox = this.cropBox;
+ var ratio;
+ var data;
+
+ if (this.isBuilt && this.isCropped) {
+ data = {
+ x: cropBox.left - canvas.left,
+ y: cropBox.top - canvas.top,
+ width: cropBox.width,
+ height: cropBox.height
+ };
+
+ ratio = image.width / image.naturalWidth;
+
+ $.each(data, function (i, n) {
+ n = n / ratio;
+ data[i] = isRounded ? round(n) : n;
+ });
+
+ } else {
+ data = {
+ x: 0,
+ y: 0,
+ width: 0,
+ height: 0
+ };
+ }
+
+ if (options.rotatable) {
+ data.rotate = image.rotate || 0;
+ }
+
+ if (options.scalable) {
+ data.scaleX = image.scaleX || 1;
+ data.scaleY = image.scaleY || 1;
+ }
+
+ return data;
+ },
+
+ /**
+ * Set the cropped area position and size with new data
+ *
+ * @param {Object} data
+ */
+ setData: function (data) {
+ var options = this.options;
+ var image = this.image;
+ var canvas = this.canvas;
+ var cropBoxData = {};
+ var isRotated;
+ var isScaled;
+ var ratio;
+
+ if ($.isFunction(data)) {
+ data = data.call(this.element);
+ }
+
+ if (this.isBuilt && !this.isDisabled && $.isPlainObject(data)) {
+ if (options.rotatable) {
+ if (isNumber(data.rotate) && data.rotate !== image.rotate) {
+ image.rotate = data.rotate;
+ this.isRotated = isRotated = true;
+ }
+ }
+
+ if (options.scalable) {
+ if (isNumber(data.scaleX) && data.scaleX !== image.scaleX) {
+ image.scaleX = data.scaleX;
+ isScaled = true;
+ }
+
+ if (isNumber(data.scaleY) && data.scaleY !== image.scaleY) {
+ image.scaleY = data.scaleY;
+ isScaled = true;
+ }
+ }
+
+ if (isRotated) {
+ this.renderCanvas();
+ } else if (isScaled) {
+ this.renderImage();
+ }
+
+ ratio = image.width / image.naturalWidth;
+
+ if (isNumber(data.x)) {
+ cropBoxData.left = data.x * ratio + canvas.left;
+ }
+
+ if (isNumber(data.y)) {
+ cropBoxData.top = data.y * ratio + canvas.top;
+ }
+
+ if (isNumber(data.width)) {
+ cropBoxData.width = data.width * ratio;
+ }
+
+ if (isNumber(data.height)) {
+ cropBoxData.height = data.height * ratio;
+ }
+
+ this.setCropBoxData(cropBoxData);
+ }
+ },
+
+ /**
+ * Get the container size data
+ *
+ * @return {Object} data
+ */
+ getContainerData: function () {
+ return this.isBuilt ? this.container : {};
+ },
+
+ /**
+ * Get the image position and size data
+ *
+ * @return {Object} data
+ */
+ getImageData: function () {
+ return this.isLoaded ? this.image : {};
+ },
+
+ /**
+ * Get the canvas position and size data
+ *
+ * @return {Object} data
+ */
+ getCanvasData: function () {
+ var canvas = this.canvas;
+ var data = {};
+
+ if (this.isBuilt) {
+ $.each([
+ 'left',
+ 'top',
+ 'width',
+ 'height',
+ 'naturalWidth',
+ 'naturalHeight'
+ ], function (i, n) {
+ data[n] = canvas[n];
+ });
+ }
+
+ return data;
+ },
+
+ /**
+ * Set the canvas position and size with new data
+ *
+ * @param {Object} data
+ */
+ setCanvasData: function (data) {
+ var canvas = this.canvas;
+ var aspectRatio = canvas.aspectRatio;
+
+ if ($.isFunction(data)) {
+ data = data.call(this.$element);
+ }
+
+ if (this.isBuilt && !this.isDisabled && $.isPlainObject(data)) {
+ if (isNumber(data.left)) {
+ canvas.left = data.left;
+ }
+
+ if (isNumber(data.top)) {
+ canvas.top = data.top;
+ }
+
+ if (isNumber(data.width)) {
+ canvas.width = data.width;
+ canvas.height = data.width / aspectRatio;
+ } else if (isNumber(data.height)) {
+ canvas.height = data.height;
+ canvas.width = data.height * aspectRatio;
+ }
+
+ this.renderCanvas(true);
+ }
+ },
+
+ /**
+ * Get the crop box position and size data
+ *
+ * @return {Object} data
+ */
+ getCropBoxData: function () {
+ var cropBox = this.cropBox;
+ var data;
+
+ if (this.isBuilt && this.isCropped) {
+ data = {
+ left: cropBox.left,
+ top: cropBox.top,
+ width: cropBox.width,
+ height: cropBox.height
+ };
+ }
+
+ return data || {};
+ },
+
+ /**
+ * Set the crop box position and size with new data
+ *
+ * @param {Object} data
+ */
+ setCropBoxData: function (data) {
+ var cropBox = this.cropBox;
+ var aspectRatio = this.options.aspectRatio;
+ var isWidthChanged;
+ var isHeightChanged;
+
+ if ($.isFunction(data)) {
+ data = data.call(this.$element);
+ }
+
+ if (this.isBuilt && this.isCropped && !this.isDisabled && $.isPlainObject(data)) {
+
+ if (isNumber(data.left)) {
+ cropBox.left = data.left;
+ }
+
+ if (isNumber(data.top)) {
+ cropBox.top = data.top;
+ }
+
+ if (isNumber(data.width)) {
+ isWidthChanged = true;
+ cropBox.width = data.width;
+ }
+
+ if (isNumber(data.height)) {
+ isHeightChanged = true;
+ cropBox.height = data.height;
+ }
+
+ if (aspectRatio) {
+ if (isWidthChanged) {
+ cropBox.height = cropBox.width / aspectRatio;
+ } else if (isHeightChanged) {
+ cropBox.width = cropBox.height * aspectRatio;
+ }
+ }
+
+ this.renderCropBox();
+ }
+ },
+
+ /**
+ * Get a canvas drawn the cropped image
+ *
+ * @param {Object} options (optional)
+ * @return {HTMLCanvasElement} canvas
+ */
+ getCroppedCanvas: function (options) {
+ var originalWidth;
+ var originalHeight;
+ var canvasWidth;
+ var canvasHeight;
+ var scaledWidth;
+ var scaledHeight;
+ var scaledRatio;
+ var aspectRatio;
+ var canvas;
+ var context;
+ var data;
+
+ if (!this.isBuilt || !this.isCropped || !SUPPORT_CANVAS) {
+ return;
+ }
+
+ if (!$.isPlainObject(options)) {
+ options = {};
+ }
+
+ data = this.getData();
+ originalWidth = data.width;
+ originalHeight = data.height;
+ aspectRatio = originalWidth / originalHeight;
+
+ if ($.isPlainObject(options)) {
+ scaledWidth = options.width;
+ scaledHeight = options.height;
+
+ if (scaledWidth) {
+ scaledHeight = scaledWidth / aspectRatio;
+ scaledRatio = scaledWidth / originalWidth;
+ } else if (scaledHeight) {
+ scaledWidth = scaledHeight * aspectRatio;
+ scaledRatio = scaledHeight / originalHeight;
+ }
+ }
+
+ // The canvas element will use `Math.floor` on a float number, so floor first
+ canvasWidth = floor(scaledWidth || originalWidth);
+ canvasHeight = floor(scaledHeight || originalHeight);
+
+ canvas = $('<canvas>')[0];
+ canvas.width = canvasWidth;
+ canvas.height = canvasHeight;
+ context = canvas.getContext('2d');
+
+ if (options.fillColor) {
+ context.fillStyle = options.fillColor;
+ context.fillRect(0, 0, canvasWidth, canvasHeight);
+ }
+
+ // https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D.drawImage
+ context.drawImage.apply(context, (function () {
+ var source = getSourceCanvas(this.$clone[0], this.image);
+ var sourceWidth = source.width;
+ var sourceHeight = source.height;
+ var args = [source];
+
+ // Source canvas
+ var srcX = data.x;
+ var srcY = data.y;
+ var srcWidth;
+ var srcHeight;
+
+ // Destination canvas
+ var dstX;
+ var dstY;
+ var dstWidth;
+ var dstHeight;
+
+ if (srcX <= -originalWidth || srcX > sourceWidth) {
+ srcX = srcWidth = dstX = dstWidth = 0;
+ } else if (srcX <= 0) {
+ dstX = -srcX;
+ srcX = 0;
+ srcWidth = dstWidth = min(sourceWidth, originalWidth + srcX);
+ } else if (srcX <= sourceWidth) {
+ dstX = 0;
+ srcWidth = dstWidth = min(originalWidth, sourceWidth - srcX);
+ }
+
+ if (srcWidth <= 0 || srcY <= -originalHeight || srcY > sourceHeight) {
+ srcY = srcHeight = dstY = dstHeight = 0;
+ } else if (srcY <= 0) {
+ dstY = -srcY;
+ srcY = 0;
+ srcHeight = dstHeight = min(sourceHeight, originalHeight + srcY);
+ } else if (srcY <= sourceHeight) {
+ dstY = 0;
+ srcHeight = dstHeight = min(originalHeight, sourceHeight - srcY);
+ }
+
+ // All the numerical parameters should be integer for `drawImage` (#476)
+ args.push(floor(srcX), floor(srcY), floor(srcWidth), floor(srcHeight));
+
+ // Scale destination sizes
+ if (scaledRatio) {
+ dstX *= scaledRatio;
+ dstY *= scaledRatio;
+ dstWidth *= scaledRatio;
+ dstHeight *= scaledRatio;
+ }
+
+ // Avoid "IndexSizeError" in IE and Firefox
+ if (dstWidth > 0 && dstHeight > 0) {
+ args.push(floor(dstX), floor(dstY), floor(dstWidth), floor(dstHeight));
+ }
+
+ return args;
+ }).call(this));
+
+ return canvas;
+ },
+
+ /**
+ * Change the aspect ratio of the crop box
+ *
+ * @param {Number} aspectRatio
+ */
+ setAspectRatio: function (aspectRatio) {
+ var options = this.options;
+
+ if (!this.isDisabled && !isUndefined(aspectRatio)) {
+
+ // 0 -> NaN
+ options.aspectRatio = max(0, aspectRatio) || NaN;
+
+ if (this.isBuilt) {
+ this.initCropBox();
+
+ if (this.isCropped) {
+ this.renderCropBox();
+ }
+ }
+ }
+ },
+
+ /**
+ * Change the drag mode
+ *
+ * @param {String} mode (optional)
+ */
+ setDragMode: function (mode) {
+ var options = this.options;
+ var croppable;
+ var movable;
+
+ if (this.isLoaded && !this.isDisabled) {
+ croppable = mode === ACTION_CROP;
+ movable = options.movable && mode === ACTION_MOVE;
+ mode = (croppable || movable) ? mode : ACTION_NONE;
+
+ this.$dragBox.
+ data(DATA_ACTION, mode).
+ toggleClass(CLASS_CROP, croppable).
+ toggleClass(CLASS_MOVE, movable);
+
+ if (!options.cropBoxMovable) {
+
+ // Sync drag mode to crop box when it is not movable(#300)
+ this.$face.
+ data(DATA_ACTION, mode).
+ toggleClass(CLASS_CROP, croppable).
+ toggleClass(CLASS_MOVE, movable);
+ }
+ }
+ }
+ };
+
+ Cropper.DEFAULTS = {
+
+ // Define the view mode of the cropper
+ viewMode: 0, // 0, 1, 2, 3
+
+ // Define the dragging mode of the cropper
+ dragMode: 'crop', // 'crop', 'move' or 'none'
+
+ // Define the aspect ratio of the crop box
+ aspectRatio: NaN,
+
+ // An object with the previous cropping result data
+ data: null,
+
+ // A jQuery selector for adding extra containers to preview
+ preview: '',
+
+ // Re-render the cropper when resize the window
+ responsive: true,
+
+ // Restore the cropped area after resize the window
+ restore: true,
+
+ // Check if the current image is a cross-origin image
+ checkCrossOrigin: true,
+
+ // Check the current image's Exif Orientation information
+ checkOrientation: true,
+
+ // Show the black modal
+ modal: true,
+
+ // Show the dashed lines for guiding
+ guides: true,
+
+ // Show the center indicator for guiding
+ center: true,
+
+ // Show the white modal to highlight the crop box
+ highlight: true,
+
+ // Show the grid background
+ background: true,
+
+ // Enable to crop the image automatically when initialize
+ autoCrop: true,
+
+ // Define the percentage of automatic cropping area when initializes
+ autoCropArea: 0.8,
+
+ // Enable to move the image
+ movable: true,
+
+ // Enable to rotate the image
+ rotatable: true,
+
+ // Enable to scale the image
+ scalable: true,
+
+ // Enable to zoom the image
+ zoomable: true,
+
+ // Enable to zoom the image by dragging touch
+ zoomOnTouch: true,
+
+ // Enable to zoom the image by wheeling mouse
+ zoomOnWheel: true,
+
+ // Define zoom ratio when zoom the image by wheeling mouse
+ wheelZoomRatio: 0.1,
+
+ // Enable to move the crop box
+ cropBoxMovable: true,
+
+ // Enable to resize the crop box
+ cropBoxResizable: true,
+
+ // Toggle drag mode between "crop" and "move" when click twice on the cropper
+ toggleDragModeOnDblclick: true,
+
+ // Size limitation
+ minCanvasWidth: 0,
+ minCanvasHeight: 0,
+ minCropBoxWidth: 0,
+ minCropBoxHeight: 0,
+ minContainerWidth: 200,
+ minContainerHeight: 100,
+
+ // Shortcuts of events
+ build: null,
+ built: null,
+ cropstart: null,
+ cropmove: null,
+ cropend: null,
+ crop: null,
+ zoom: null
+ };
+
+ Cropper.setDefaults = function (options) {
+ $.extend(Cropper.DEFAULTS, options);
+ };
+
+ Cropper.TEMPLATE = (
+ '<div class="cropper-container">' +
+ '<div class="cropper-wrap-box">' +
+ '<div class="cropper-canvas"></div>' +
+ '</div>' +
+ '<div class="cropper-drag-box"></div>' +
+ '<div class="cropper-crop-box">' +
+ '<span class="cropper-view-box"></span>' +
+ '<span class="cropper-dashed dashed-h"></span>' +
+ '<span class="cropper-dashed dashed-v"></span>' +
+ '<span class="cropper-center"></span>' +
+ '<span class="cropper-face"></span>' +
+ '<span class="cropper-line line-e" data-action="e"></span>' +
+ '<span class="cropper-line line-n" data-action="n"></span>' +
+ '<span class="cropper-line line-w" data-action="w"></span>' +
+ '<span class="cropper-line line-s" data-action="s"></span>' +
+ '<span class="cropper-point point-e" data-action="e"></span>' +
+ '<span class="cropper-point point-n" data-action="n"></span>' +
+ '<span class="cropper-point point-w" data-action="w"></span>' +
+ '<span class="cropper-point point-s" data-action="s"></span>' +
+ '<span class="cropper-point point-ne" data-action="ne"></span>' +
+ '<span class="cropper-point point-nw" data-action="nw"></span>' +
+ '<span class="cropper-point point-sw" data-action="sw"></span>' +
+ '<span class="cropper-point point-se" data-action="se"></span>' +
+ '</div>' +
+ '</div>'
+ );
+
+ // Save the other cropper
+ Cropper.other = $.fn.cropper;
+
+ // Register as jQuery plugin
+ $.fn.cropper = function (option) {
+ var args = toArray(arguments, 1);
+ var result;
+
+ this.each(function () {
+ var $this = $(this);
+ var data = $this.data(NAMESPACE);
+ var options;
+ var fn;
+
+ if (!data) {
+ if (/destroy/.test(option)) {
+ return;
+ }
+
+ options = $.extend({}, $this.data(), $.isPlainObject(option) && option);
+ $this.data(NAMESPACE, (data = new Cropper(this, options)));
+ }
+
+ if (typeof option === 'string' && $.isFunction(fn = data[option])) {
+ result = fn.apply(data, args);
+ }
+ });
+
+ return isUndefined(result) ? this : result;
+ };
+
+ $.fn.cropper.Constructor = Cropper;
+ $.fn.cropper.setDefaults = Cropper.setDefaults;
+
+ // No conflict
+ $.fn.cropper.noConflict = function () {
+ $.fn.cropper = Cropper.other;
+ return this;
+ };
+
+});
diff --git a/vendor/assets/stylesheets/cropper.css b/vendor/assets/stylesheets/cropper.css
new file mode 100755
index 00000000000..41ee4bd546c
--- /dev/null
+++ b/vendor/assets/stylesheets/cropper.css
@@ -0,0 +1,379 @@
+/*!
+ * Cropper v2.2.5
+ * https://github.com/fengyuanchen/cropper
+ *
+ * Copyright (c) 2014-2016 Fengyuan Chen and contributors
+ * Released under the MIT license
+ *
+ * Date: 2016-01-18T05:42:29.639Z
+ */
+.cropper-container {
+ font-size: 0;
+ line-height: 0;
+
+ position: relative;
+
+ -webkit-user-select: none;
+ -moz-user-select: none;
+ -ms-user-select: none;
+ user-select: none;
+
+ direction: ltr !important;
+ -ms-touch-action: none;
+ touch-action: none;
+ -webkit-tap-highlight-color: transparent;
+ -webkit-touch-callout: none;
+}
+
+.cropper-container img {
+ display: block;
+
+ width: 100%;
+ min-width: 0 !important;
+ max-width: none !important;
+ height: 100%;
+ min-height: 0 !important;
+ max-height: none !important;
+
+ image-orientation: 0deg !important;
+}
+
+.cropper-wrap-box,
+.cropper-canvas,
+.cropper-drag-box,
+.cropper-crop-box,
+.cropper-modal {
+ position: absolute;
+ top: 0;
+ right: 0;
+ bottom: 0;
+ left: 0;
+}
+
+.cropper-wrap-box {
+ overflow: hidden;
+}
+
+.cropper-drag-box {
+ opacity: 0;
+ background-color: #fff;
+
+ filter: alpha(opacity=0);
+}
+
+.cropper-modal {
+ opacity: .5;
+ background-color: #000;
+
+ filter: alpha(opacity=50);
+}
+
+.cropper-view-box {
+ display: block;
+ overflow: hidden;
+
+ width: 100%;
+ height: 100%;
+
+ outline: 1px solid #39f;
+ outline-color: rgba(51, 153, 255, .75);
+}
+
+.cropper-dashed {
+ position: absolute;
+
+ display: block;
+
+ opacity: .5;
+ border: 0 dashed #eee;
+
+ filter: alpha(opacity=50);
+}
+
+.cropper-dashed.dashed-h {
+ top: 33.33333%;
+ left: 0;
+
+ width: 100%;
+ height: 33.33333%;
+
+ border-top-width: 1px;
+ border-bottom-width: 1px;
+}
+
+.cropper-dashed.dashed-v {
+ top: 0;
+ left: 33.33333%;
+
+ width: 33.33333%;
+ height: 100%;
+
+ border-right-width: 1px;
+ border-left-width: 1px;
+}
+
+.cropper-center {
+ position: absolute;
+ top: 50%;
+ left: 50%;
+
+ display: block;
+
+ width: 0;
+ height: 0;
+
+ opacity: .75;
+
+ filter: alpha(opacity=75);
+}
+
+.cropper-center:before,
+.cropper-center:after {
+ position: absolute;
+
+ display: block;
+
+ content: ' ';
+
+ background-color: #eee;
+}
+
+.cropper-center:before {
+ top: 0;
+ left: -3px;
+
+ width: 7px;
+ height: 1px;
+}
+
+.cropper-center:after {
+ top: -3px;
+ left: 0;
+
+ width: 1px;
+ height: 7px;
+}
+
+.cropper-face,
+.cropper-line,
+.cropper-point {
+ position: absolute;
+
+ display: block;
+
+ width: 100%;
+ height: 100%;
+
+ opacity: .1;
+
+ filter: alpha(opacity=10);
+}
+
+.cropper-face {
+ top: 0;
+ left: 0;
+
+ background-color: #fff;
+}
+
+.cropper-line {
+ background-color: #39f;
+}
+
+.cropper-line.line-e {
+ top: 0;
+ right: -3px;
+
+ width: 5px;
+
+ cursor: e-resize;
+}
+
+.cropper-line.line-n {
+ top: -3px;
+ left: 0;
+
+ height: 5px;
+
+ cursor: n-resize;
+}
+
+.cropper-line.line-w {
+ top: 0;
+ left: -3px;
+
+ width: 5px;
+
+ cursor: w-resize;
+}
+
+.cropper-line.line-s {
+ bottom: -3px;
+ left: 0;
+
+ height: 5px;
+
+ cursor: s-resize;
+}
+
+.cropper-point {
+ width: 5px;
+ height: 5px;
+
+ opacity: .75;
+ background-color: #39f;
+
+ filter: alpha(opacity=75);
+}
+
+.cropper-point.point-e {
+ top: 50%;
+ right: -3px;
+
+ margin-top: -3px;
+
+ cursor: e-resize;
+}
+
+.cropper-point.point-n {
+ top: -3px;
+ left: 50%;
+
+ margin-left: -3px;
+
+ cursor: n-resize;
+}
+
+.cropper-point.point-w {
+ top: 50%;
+ left: -3px;
+
+ margin-top: -3px;
+
+ cursor: w-resize;
+}
+
+.cropper-point.point-s {
+ bottom: -3px;
+ left: 50%;
+
+ margin-left: -3px;
+
+ cursor: s-resize;
+}
+
+.cropper-point.point-ne {
+ top: -3px;
+ right: -3px;
+
+ cursor: ne-resize;
+}
+
+.cropper-point.point-nw {
+ top: -3px;
+ left: -3px;
+
+ cursor: nw-resize;
+}
+
+.cropper-point.point-sw {
+ bottom: -3px;
+ left: -3px;
+
+ cursor: sw-resize;
+}
+
+.cropper-point.point-se {
+ right: -3px;
+ bottom: -3px;
+
+ width: 20px;
+ height: 20px;
+
+ cursor: se-resize;
+
+ opacity: 1;
+
+ filter: alpha(opacity=100);
+}
+
+.cropper-point.point-se:before {
+ position: absolute;
+ right: -50%;
+ bottom: -50%;
+
+ display: block;
+
+ width: 200%;
+ height: 200%;
+
+ content: ' ';
+
+ opacity: 0;
+ background-color: #39f;
+
+ filter: alpha(opacity=0);
+}
+
+@media (min-width: 768px) {
+ .cropper-point.point-se {
+ width: 15px;
+ height: 15px;
+ }
+}
+
+@media (min-width: 992px) {
+ .cropper-point.point-se {
+ width: 10px;
+ height: 10px;
+ }
+}
+
+@media (min-width: 1200px) {
+ .cropper-point.point-se {
+ width: 5px;
+ height: 5px;
+
+ opacity: .75;
+
+ filter: alpha(opacity=75);
+ }
+}
+
+.cropper-invisible {
+ opacity: 0;
+
+ filter: alpha(opacity=0);
+}
+
+.cropper-bg {
+ background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQAQMAAAAlPW0iAAAAA3NCSVQICAjb4U/gAAAABlBMVEXMzMz////TjRV2AAAACXBIWXMAAArrAAAK6wGCiw1aAAAAHHRFWHRTb2Z0d2FyZQBBZG9iZSBGaXJld29ya3MgQ1M26LyyjAAAABFJREFUCJlj+M/AgBVhF/0PAH6/D/HkDxOGAAAAAElFTkSuQmCC');
+}
+
+.cropper-hide {
+ position: absolute;
+
+ display: block;
+
+ width: 0;
+ height: 0;
+}
+
+.cropper-hidden {
+ display: none !important;
+}
+
+.cropper-move {
+ cursor: move;
+}
+
+.cropper-crop {
+ cursor: crosshair;
+}
+
+.cropper-disabled .cropper-drag-box,
+.cropper-disabled .cropper-face,
+.cropper-disabled .cropper-line,
+.cropper-disabled .cropper-point {
+ cursor: not-allowed;
+}