diff --git a/.gitignore b/.gitignore
index 91ea81bfc4..1eb785451f 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 d803f3c860..8a729f957a 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -5,11 +5,16 @@ services:
- postgres:latest
- redis:latest
+cache:
+ key: "ruby21"
+ paths:
+ - vendor
+
variables:
MYSQL_ALLOW_EMPTY_PASSWORD: "1"
before_script:
- - ./scripts/prepare_build.sh
+ - source ./scripts/prepare_build.sh
- ruby -v
- which ruby
- gem install bundler --no-ri --no-rdoc
@@ -17,7 +22,7 @@ before_script:
- touch log/application.log
- touch log/test.log
- bundle install --without postgres production --jobs $(nproc) "${FLAGS[@]}"
- - bundle exec rake db:reset db:create RAILS_ENV=test
+ - RAILS_ENV=test bundle exec rake db:drop db:create db:schema:load db:migrate
spec:feature:
script:
@@ -127,10 +132,155 @@ flay:
- mysql
bundler:audit:
- script:
+ script:
- "bundle exec bundle-audit update"
- "bundle exec bundle-audit check"
tags:
- ruby
- mysql
allow_failure: true
+
+# Ruby 2.2 jobs
+
+spec:feature:ruby22:
+ 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: "ruby22"
+ paths:
+ - vendor
+ tags:
+ - ruby
+ - mysql
+
+spec:api:ruby22:
+ image: ruby:2.2
+ only:
+ - master
+ script:
+ - RAILS_ENV=test SIMPLECOV=true bundle exec rake spec:api
+ cache:
+ key: "ruby22"
+ paths:
+ - vendor
+ tags:
+ - ruby
+ - mysql
+
+spec:models:ruby22:
+ image: ruby:2.2
+ only:
+ - master
+ script:
+ - RAILS_ENV=test SIMPLECOV=true bundle exec rake spec:models
+ cache:
+ key: "ruby22"
+ paths:
+ - vendor
+ tags:
+ - ruby
+ - mysql
+
+spec:lib:ruby22:
+ image: ruby:2.2
+ only:
+ - master
+ script:
+ - RAILS_ENV=test SIMPLECOV=true bundle exec rake spec:lib
+ cache:
+ key: "ruby22"
+ paths:
+ - vendor
+ tags:
+ - ruby
+ - mysql
+
+spec:services:ruby22:
+ image: ruby:2.2
+ only:
+ - master
+ script:
+ - RAILS_ENV=test SIMPLECOV=true bundle exec rake spec:services
+ cache:
+ key: "ruby22"
+ paths:
+ - vendor
+ tags:
+ - ruby
+ - mysql
+
+spec:benchmark:ruby22:
+ image: ruby:2.2
+ only:
+ - master
+ script:
+ - RAILS_ENV=test bundle exec rake spec:benchmark
+ cache:
+ key: "ruby22"
+ paths:
+ - vendor
+ tags:
+ - ruby
+ - mysql
+ allow_failure: true
+
+spec:other:ruby22:
+ image: ruby:2.2
+ only:
+ - master
+ script:
+ - RAILS_ENV=test SIMPLECOV=true bundle exec rake spec:other
+ cache:
+ key: "ruby22"
+ paths:
+ - vendor
+ tags:
+ - ruby
+ - mysql
+
+spinach:project:half:ruby22:
+ image: ruby:2.2
+ only:
+ - master
+ script:
+ - RAILS_ENV=test SIMPLECOV=true bundle exec rake spinach:project:half
+ cache:
+ key: "ruby22"
+ paths:
+ - vendor
+ tags:
+ - ruby
+ - mysql
+
+spinach:project:rest:ruby22:
+ image: ruby:2.2
+ only:
+ - master
+ script:
+ - RAILS_ENV=test SIMPLECOV=true bundle exec rake spinach:project:rest
+ cache:
+ key: "ruby22"
+ paths:
+ - vendor
+ tags:
+ - ruby
+ - mysql
+
+spinach:other:ruby22:
+ image: ruby:2.2
+ only:
+ - master
+ script:
+ - RAILS_ENV=test SIMPLECOV=true bundle exec rake spinach:other
+ cache:
+ key: "ruby22"
+ paths:
+ - vendor
+ tags:
+ - ruby
+ - mysql
+
diff --git a/.ruby-version b/.ruby-version
index 04b10b4f15..ebf14b4698 100644
--- a/.ruby-version
+++ b/.ruby-version
@@ -1 +1 @@
-2.1.7
+2.1.8
diff --git a/CHANGELOG b/CHANGELOG
index 24c08358b6..bb7760bfce 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -1,5 +1,163 @@
Please view this file on the master branch, on stable branches it's out of date.
+v 8.6.0 (unreleased)
+ - Contributions to forked projects are included in calendar
+ - Improve the formatting for the user page bio (Connor Shea)
+ - 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
+ - Strip leading and trailing spaces in URL validator (evuez)
+ - Return empty array instead of 404 when commit has no statuses in commit status API
+ - 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)
+ - Add shortcut to toggle markdown preview (Florent Baldino)
+ - Show labels in dashboard and group milestone views
+ - Add main language of a project in the list of projects (Tiago Botelho)
+ - Add ability to show archived projects on dashboard, explore and group pages
+
+v 8.5.8
+ - Bump Git version requirement to 2.7.4
+
+v 8.5.7
+ - Bump Git version requirement to 2.7.3
+
+v 8.5.6
+ - Obtain a lease before querying LDAP
+
+v 8.5.5
+ - Ensure removing a project removes associated Todo entries
+ - Prevent a 500 error in Todos when author was removed
+ - Fix pagination for filtered dashboard and explore pages
+ - Fix "Show all" link behavior
+
+v 8.5.4
+ - Do not cache requests for badges (including builds badge)
+
+v 8.5.3
+ - Flush repository caches before renaming projects
+
+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
+
+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
+ - Update New Relic gem to 3.14.1.311 (Stan Hu)
+ - Add "visibility" flag to GET /projects api endpoint
+ - Add an option to supply root email through an environmental variable (Koichiro Mikami)
+ - Ignore binary files in code search to prevent Error 500 (Stan Hu)
+ - Render sanitized SVG images (Stan Hu)
+ - Support download access by PRIVATE-TOKEN header (Stan Hu)
+ - Upgrade gitlab_git to 7.2.23 to fix commit message mentions in first branch push
+ - Add option to include the sender name in body of Notify email (Jason Lee)
+ - New UI for pagination
+ - Don't prevent sign out when 2FA enforcement is enabled and user hasn't yet
+ set it up
+ - API: Added "merge_requests/:merge_request_id/closes_issues" (Gal Schlezinger)
+ - Fix diff comments loaded by AJAX to load comment with diff in discussion tab
+ - Fix relative links in other markup formats (Ben Boeckel)
+ - Whitelist raw "abbr" elements when parsing Markdown (Benedict Etzel)
+ - Fix label links for a merge request pointing to issues list
+ - Don't vendor minified JS
+ - Increase project import timeout to 15 minutes
+ - Be more permissive with email address validation: it only has to contain a single '@'
+ - Display 404 error on group not found
+ - Track project import failure
+ - Support Two-factor Authentication for LDAP users
+ - Display database type and version in Administration dashboard
+ - Allow limited Markdown in Broadcast Messages
+ - Fix visibility level text in admin area (Zeger-Jan van de Weg)
+ - Warn admin during OAuth of granting admin rights (Zeger-Jan van de Weg)
+ - 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)
+ - Fix API to keep request parameters in Link header (Michael Potthoff)
+ - 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.4
+ - Update omniauth-saml gem to 1.4.2
+ - Prevent long-running backup tasks from timing out the database connection
+ - Add a Project setting to allow guests to view build logs (defaults to true)
+ - Sort project milestones by due date including issue editor (Oliver Rogers / Orih)
+
v 8.4.3
- Increase lfs_objects size column to 8-byte integer to allow files larger
than 2.1GB
@@ -22,12 +180,14 @@ v 8.4.2
track them in Performance Monitoring.
- Increase contrast between highlighted code comments and inline diff marker
- Fix method undefined when using external commit status in builds
+ - Fix highlighting in blame view.
v 8.4.1
- Apply security updates for Rails (4.2.5.1), rails-html-sanitizer (1.0.3),
and Nokogiri (1.6.7.2)
- Fix redirect loop during import
- Fix diff highlighting for all syntax themes
+ - Delete project and associations in a background worker
v 8.4.0
- Allow LDAP users to change their email if it was not set by the LDAP server
@@ -71,7 +231,7 @@ v 8.4.0
- Show 'All' tab by default in the builds page
- Add Open Graph and Twitter Card data to all pages
- Fix API project lookups when querying with a namespace with dots (Stan Hu)
- - Enable forcing Two-Factor authentication sitewide, with optional grace period
+ - Enable forcing Two-factor authentication sitewide, with optional grace period
- Import GitHub Pull Requests into GitLab
- Change single user API endpoint to return more detailed data (Michael Potthoff)
- Update version check images to use SVG
@@ -149,6 +309,7 @@ v 8.3.0
- Handle and report SSL errors in Web hook test (Stan Hu)
- Bump Redis requirement to 2.8 for Sidekiq 4 (Stan Hu)
- Fix: Assignee selector is empty when 'Unassigned' is selected (Jose Corcuera)
+ - WIP identifier on merge requests no longer requires trailing space
- Add rake tasks for git repository maintainance (Zeger-Jan van de Weg)
- Fix 500 error when update group member permission
- Fix: As an admin, cannot add oneself as a member to a group/project
@@ -331,6 +492,7 @@ v 8.1.0
- Improved performance of the trending projects page
- Remove CI migration task
- Improved performance of finding projects by their namespace
+ - Add assignee data to Issuables' hook_data (Bram Daams)
- Fix bug where transferring a project would result in stale commit links (Stan Hu)
- Fix build trace updating
- Include full path of source and target branch names in New Merge Request page (Stan Hu)
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index 1eabbdc5ca..c4522998f4 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -1,3 +1,29 @@
+
+
+**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)
+
+
+
# Contribute to GitLab
Thank you for your interest in contributing to GitLab. This guide details how
@@ -147,7 +173,7 @@ sudo -u git -H bundle exec rake gitlab:check RAILS_ENV=production SANITIZE=true)
sudo gitlab-rake gitlab:env:info)
(For installations from source run and paste the output of:
-sudo -u git -H bundle exec rake gitlab:env:info)
+sudo -u git -H bundle exec rake gitlab:env:info RAILS_ENV=production)
## Possible fixes
@@ -177,6 +203,26 @@ is probably 1, adding a new Git Hook maybe 4 or 5, big features 7-9.
issues or chunks. You can simply not set the weight of a parent issue and set
weights to children issues.
+### Regression issues
+
+Every monthly release has a corresponding issue on the CE issue tracker to keep
+track of functionality broken by that release and any fixes that need to be
+included in a patch release (see [8.3 Regressions] as an example).
+
+As outlined in the issue description, the intended workflow is to post one note
+with a reference to an issue describing the regression, and then to update that
+note with a reference to the merge request that fixes it as it becomes available.
+
+If you're a contributor who doesn't have the required permissions to update
+other users' notes, please post a new note with a reference to both the issue
+and the merge request.
+
+The release manager will [update the notes] in the regression issue as fixes are
+addressed.
+
+[8.3 Regressions]: https://gitlab.com/gitlab-org/gitlab-ce/issues/4127
+[update the notes]: https://gitlab.com/gitlab-org/release-tools/blob/master/doc/pro-tips.md#update-the-regression-issue
+
## Merge requests
We welcome merge requests with fixes and improvements to GitLab code, tests,
@@ -214,15 +260,17 @@ request is as follows:
1. Add your changes to the [CHANGELOG](CHANGELOG)
1. If you are changing the README, some documentation or other things which
have no effect on the tests, add `[ci skip]` somewhere in the commit message
+ and make sure to read the [documentation styleguide][doc-styleguide]
1. If you have multiple commits please combine them into one commit by
[squashing them][git-squash]
1. Push the commit(s) to your fork
1. Submit a merge request (MR) to the master branch
1. The MR title should describe the change you want to make
1. The MR description should give a motive for your change and the method you
- used to achieve it
+ used to achieve it, see the [merge request description format]
+ (#merge-request-description-format)
1. If the MR changes the UI it should include before and after screenshots
-1. If the MR changes CSS classes please include the list of affected pages
+1. If the MR changes CSS classes please include the list of affected pages,
`grep css-class ./app -R`
1. Link any relevant [issues][ce-tracker] in the merge request description and
leave a comment on them with a link back to the MR
@@ -255,6 +303,69 @@ For examples of feedback on merge requests please look at already
request feel free to mention one of the Merge Marshalls of the [core 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.
+
+### Merge request description format
+
+Please submit merge requests using the following template in the merge request
+description area. Copy-paste it to retain the markdown format.
+
+```
+## What does this MR do?
+
+## Are there points in the code the reviewer needs to double check?
+
+## Why was this MR needed?
+
+## What are the relevant issue numbers?
+
+## Screenshots (if relevant)
+```
+
+### Contribution acceptance criteria
+
+1. The change is as small as possible
+1. Include proper tests and make all tests pass (unless it contains a test
+ exposing a bug in existing code)
+1. If you suspect a failing CI build is unrelated to your contribution, you may
+ try and restart the failing CI job or ask a developer to fix the
+ aforementioned failing test
+1. Your MR initially contains a single commit (please use `git rebase -i` to
+ squash commits)
+1. Your changes can merge without problems (if not please merge `master`, never
+ rebase commits pushed to the remote server)
+1. Does not break any existing functionality
+1. Fixes one specific issue or implements one specific feature (do not combine
+ things, send separate merge requests if needed)
+1. Migrations should do only one thing (e.g., either create a table, move data
+ 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. 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.
+1. It conforms to the [style guides](#style-guides) and the following:
+ - If your change touches a line that does not follow the style, modify the
+ entire line to follow it. This prevents linting tools from generating warnings.
+ - Don't touch neighbouring lines. As an exception, automatic mass
+ refactoring modifications may leave style non-compliant.
+
+## Changes for Stable Releases
+
+Sometimes certain changes have to be added to an existing stable release.
+Two examples are bug fixes and performance improvements. In these cases the
+corresponding merge request should be updated to have the following:
+
+1. A milestone indicating what release the merge request should be merged into.
+1. The label "Pick into Stable"
+
+This makes it easier for release managers to keep track of what still has to be
+merged and where changes have to be merged into.
+Like all merge requests the target should be master so all bugfixes are in master.
+
## Definition of done
If you contribute to GitLab please know that changes involve more than just
@@ -264,7 +375,7 @@ the feature you contribute through all of these steps.
1. Description explaining the relevancy (see following item)
1. Working and clean code that is commented where needed
1. Unit and integration tests that pass on the CI server
-1. Documented in the /doc directory
+1. [Documented][doc-styleguide] in the /doc directory
1. Changelog entry added
1. Reviewed and any concerns are addressed
1. Merged by the project lead
@@ -285,43 +396,6 @@ merge request:
1. Test suite https://gitlab.com/gitlab-org/gitlab-ce/blob/master/scripts/prepare_build.sh
1. Omnibus package creator https://gitlab.com/gitlab-org/omnibus-gitlab
-## Merge request description format
-
-1. What does this MR do?
-1. Are there points in the code the reviewer needs to double check?
-1. Why was this MR needed?
-1. What are the relevant issue numbers?
-1. Screenshots (if relevant)
-
-## Contribution acceptance criteria
-
-1. The change is as small as possible (see the above paragraph for details)
-1. Include proper tests and make all tests pass (unless it contains a test
- exposing a bug in existing code)
-1. If you suspect a failing CI build is unrelated to your contribution, you may
- try and restart the failing CI job or ask a developer to fix the
- aforementioned failing test
-1. Your MR initially contains a single commit (please use `git rebase -i` to
- squash commits)
-1. Your changes can merge without problems (if not please merge `master`, never
- rebase commits pushed to the remote server)
-1. Does not break any existing functionality
-1. Fixes one specific issue or implements one specific feature (do not combine
- things, send separate merge requests if needed)
-1. Migrations should do only one thing (eg: either create a table, move data 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. 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.
-1. It conforms to the following style guides:
- * If your change touches a line that does not follow the style, modify the
- entire line to follow it. This prevents linting tools from generating warnings.
- * Don't touch neighbouring lines. As an exception, automatic mass
- refactoring modifications may leave style non-compliant.
-
## Style guides
1. [Ruby](https://github.com/bbatsov/ruby-style-guide).
@@ -336,7 +410,7 @@ merge request:
contributors to enhance security
1. [Database Migrations](doc/development/migration_style_guide.md)
1. [Markdown](http://www.cirosantilli.com/markdown-styleguide)
-1. [Documentation styleguide](doc/development/doc_styleguide.md)
+1. [Documentation styleguide][doc-styleguide]
1. Interface text should be written subjectively instead of objectively. It
should be the GitLab core team addressing a person. It should be written in
present time and never use past tense (has been/was). For example instead
@@ -377,7 +451,7 @@ reported by emailing `contact@gitlab.com`.
This Code of Conduct is adapted from the [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/
+[core-team]: https://about.gitlab.com/core-team/
[getting help page]: 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
@@ -398,3 +472,4 @@ available at [http://contributor-covenant.org/version/1/1/0/](http://contributor
[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"
diff --git a/GITLAB_WORKHORSE_VERSION b/GITLAB_WORKHORSE_VERSION
index b616048743..d2b13eb644 100644
--- a/GITLAB_WORKHORSE_VERSION
+++ b/GITLAB_WORKHORSE_VERSION
@@ -1 +1 @@
-0.6.2
+0.6.4
diff --git a/Gemfile b/Gemfile
index acd187400a..db0e7d9766 100644
--- a/Gemfile
+++ b/Gemfile
@@ -1,6 +1,6 @@
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
@@ -21,7 +21,7 @@ gem "pg", '~> 0.18.2', group: :postgres
gem 'devise', '~> 3.5.4'
gem 'devise-async', '~> 0.9.0'
gem 'doorkeeper', '~> 2.2.0'
-gem 'omniauth', '~> 1.2.2'
+gem 'omniauth', '~> 1.3.1'
gem 'omniauth-azure-oauth2', '~> 0.0.6'
gem 'omniauth-bitbucket', '~> 0.0.2'
gem 'omniauth-cas3', '~> 1.1.2'
@@ -30,14 +30,15 @@ gem 'omniauth-github', '~> 1.1.1'
gem 'omniauth-gitlab', '~> 1.0.0'
gem 'omniauth-google-oauth2', '~> 0.2.0'
gem 'omniauth-kerberos', '~> 0.3.0', group: :kerberos
-gem 'omniauth-saml', '~> 1.4.0'
+gem 'omniauth-saml', '~> 1.4.2'
gem 'omniauth-shibboleth', '~> 1.2.0'
gem 'omniauth-twitter', '~> 1.2.0'
gem 'omniauth_crowd', '~> 2.2.0'
gem 'rack-oauth2', '~> 1.2.1'
-# reCAPTCHA protection
+# Spam and anti-bot protection
gem 'recaptcha', require: 'recaptcha/rails'
+gem 'akismet', '~> 2.0'
# Two-factor authentication
gem 'devise-two-factor', '~> 2.0.0'
@@ -49,7 +50,7 @@ gem "browser", '~> 1.0.0'
# Extracting information from a git repository
# Provide access to Gitlab::Git library
-gem "gitlab_git", '~> 7.2.22'
+gem "gitlab_git", '~> 8.2'
# LDAP Auth
# GitLab fork with several improvements to original library. For full list of changes
@@ -104,7 +105,7 @@ gem 'rouge', '~> 1.10.1'
# See https://groups.google.com/forum/#!topic/ruby-security-ann/aSbgDiwb24s
# and https://groups.google.com/forum/#!topic/ruby-security-ann/Dy7YiKb_pMM
-gem 'nokogiri', '1.6.7.2'
+gem 'nokogiri', '~> 1.6.7', '>= 1.6.7.2'
# Diffs
gem 'diffy', '~> 3.0.3'
@@ -179,6 +180,9 @@ gem "underscore-rails", "~> 1.8.0"
gem "sanitize", '~> 2.0'
gem 'babosa', '~> 1.0.2'
+# Sanitizes SVG input
+gem "loofah", "~> 2.0.3"
+
# Protect against bruteforcing
gem "rack-attack", '~> 4.3.1'
@@ -200,7 +204,7 @@ 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'
@@ -213,6 +217,9 @@ gem 'select2-rails', '~> 3.5.9'
gem 'virtus', '~> 1.0.1'
gem 'net-ssh', '~> 3.0.1'
+# Sentry integration
+gem 'sentry-raven', '~> 0.15'
+
# Metrics
group :metrics do
gem 'allocations', '~> 1.0', require: false, platform: :mri
@@ -294,15 +301,11 @@ end
group :production do
gem "gitlab_meta", '7.0'
-
- # Sentry integration
- gem 'sentry-raven'
end
-gem "newrelic_rpm", '~> 3.9.4.245'
-gem 'newrelic-grape'
+gem "newrelic_rpm", '~> 3.14'
-gem 'octokit', '~> 3.7.0'
+gem 'octokit', '~> 3.8.0'
gem "mail_room", "~> 0.6.1"
diff --git a/Gemfile.lock b/Gemfile.lock
index 8128203e2f..946842b4e2 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)
@@ -49,7 +49,8 @@ GEM
addressable (2.3.8)
after_commit_queue (1.3.0)
activerecord (>= 3.0)
- allocations (1.0.3)
+ akismet (2.0.0)
+ allocations (1.0.4)
annotate (2.6.10)
activerecord (>= 3.2, <= 4.3)
rake (~> 10.4)
@@ -335,11 +336,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)
@@ -354,13 +355,13 @@ GEM
diff-lcs (~> 1.1)
mime-types (~> 1.15)
posix-spawn (~> 0.3)
- gitlab_emoji (0.2.0)
- gemojione (~> 2.1)
- gitlab_git (7.2.24)
+ gitlab_emoji (0.3.1)
+ gemojione (~> 2.2, >= 2.2.1)
+ gitlab_git (8.2.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)
@@ -478,10 +479,7 @@ GEM
net-ldap (0.12.1)
net-ssh (3.0.1)
netrc (0.11.0)
- newrelic-grape (2.1.0)
- grape
- newrelic_rpm
- newrelic_rpm (3.9.4.245)
+ newrelic_rpm (3.14.1.311)
nokogiri (1.6.7.2)
mini_portile2 (~> 2.0.0.rc2)
nprogress-rails (0.1.6.7)
@@ -492,11 +490,11 @@ GEM
multi_json (~> 1.3)
multi_xml (~> 0.5)
rack (~> 1.2)
- octokit (3.7.1)
+ octokit (3.8.0)
sawyer (~> 0.6.0, >= 0.5.3)
- omniauth (1.2.2)
+ omniauth (1.3.1)
hashie (>= 1.2, < 4)
- rack (~> 1.0)
+ rack (>= 1.0, < 3)
omniauth-azure-oauth2 (0.0.6)
jwt (~> 1.0)
omniauth (~> 1.0)
@@ -534,9 +532,9 @@ GEM
omniauth-oauth2 (1.3.1)
oauth2 (~> 1.0)
omniauth (~> 1.2)
- omniauth-saml (1.4.1)
+ omniauth-saml (1.4.2)
omniauth (~> 1.1)
- ruby-saml (~> 1.0.0)
+ ruby-saml (~> 1.1, >= 1.1.1)
omniauth-shibboleth (1.2.1)
omniauth (>= 1.0.0)
omniauth-twitter (1.2.1)
@@ -588,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)
@@ -607,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)
@@ -692,7 +690,7 @@ GEM
ruby-fogbugz (0.2.1)
crack (~> 0.4)
ruby-progressbar (1.7.5)
- ruby-saml (1.0.0)
+ ruby-saml (1.1.1)
nokogiri (>= 1.5.10)
uuid (~> 2.3)
ruby2ruby (2.2.0)
@@ -703,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)
@@ -725,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)
@@ -884,6 +882,7 @@ DEPENDENCIES
acts-as-taggable-on (~> 3.4)
addressable (~> 2.3.8)
after_commit_queue
+ akismet (~> 2.0)
allocations (~> 1.0)
annotate (~> 2.6.0)
asana (~> 0.4.0)
@@ -933,8 +932,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 (~> 7.2.22)
+ gitlab_emoji (~> 0.3.0)
+ gitlab_git (~> 8.2)
gitlab_meta (= 7.0)
gitlab_omniauth-ldap (~> 1.2.1)
gollum-lib (~> 4.1.0)
@@ -953,6 +952,7 @@ DEPENDENCIES
jquery-ui-rails (~> 5.0.0)
kaminari (~> 0.16.3)
letter_opener (~> 1.1.2)
+ loofah (~> 2.0.3)
mail_room (~> 0.6.1)
method_source (~> 0.8)
minitest (~> 5.7.0)
@@ -960,13 +960,12 @@ DEPENDENCIES
mysql2 (~> 0.3.16)
nested_form (~> 0.3.2)
net-ssh (~> 3.0.1)
- newrelic-grape
- newrelic_rpm (~> 3.9.4.245)
- nokogiri (= 1.6.7.2)
+ newrelic_rpm (~> 3.14)
+ nokogiri (~> 1.6.7, >= 1.6.7.2)
nprogress-rails (~> 0.1.6.7)
oauth2 (~> 1.0.0)
- octokit (~> 3.7.0)
- omniauth (~> 1.2.2)
+ octokit (~> 3.8.0)
+ omniauth (~> 1.3.1)
omniauth-azure-oauth2 (~> 0.0.6)
omniauth-bitbucket (~> 0.0.2)
omniauth-cas3 (~> 1.1.2)
@@ -975,7 +974,7 @@ DEPENDENCIES
omniauth-gitlab (~> 1.0.0)
omniauth-google-oauth2 (~> 0.2.0)
omniauth-kerberos (~> 0.3.0)
- omniauth-saml (~> 1.4.0)
+ omniauth-saml (~> 1.4.2)
omniauth-shibboleth (~> 1.2.0)
omniauth-twitter (~> 1.2.0)
omniauth_crowd (~> 2.2.0)
@@ -988,7 +987,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
@@ -1010,7 +1009,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)
diff --git a/README.md b/README.md
index 3ec1d4a776..afa60116eb 100644
--- a/README.md
+++ b/README.md
@@ -68,7 +68,7 @@ GitLab is a Ruby on Rails application that runs on the following software:
- Ubuntu/Debian/CentOS/RHEL
- Ruby (MRI) 2.1
-- Git 1.7.10+
+- Git 2.7.4+
- Redis 2.8+
- MySQL or PostgreSQL
diff --git a/Rakefile b/Rakefile
index 35b2f05cbb..5dd389d567 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 01129dc3ec..1336777f6b 100644
--- a/VERSION
+++ b/VERSION
@@ -1 +1 @@
-8.4.3
\ No newline at end of file
+8.5.8
\ No newline at end of file
diff --git a/app/assets/images/emoji.png b/app/assets/images/emoji.png
index a8ad7b6eab..1e7cf79ea4 100644
Binary files a/app/assets/images/emoji.png and b/app/assets/images/emoji.png differ
diff --git a/app/assets/images/emoji@2x.png b/app/assets/images/emoji@2x.png
new file mode 100644
index 0000000000..74d67f7520
Binary files /dev/null and b/app/assets/images/emoji@2x.png differ
diff --git a/app/assets/javascripts/admin.js.coffee b/app/assets/javascripts/admin.js.coffee
index eb951f7171..b2b8e1b7ff 100644
--- a/app/assets/javascripts/admin.js.coffee
+++ b/app/assets/javascripts/admin.js.coffee
@@ -12,19 +12,6 @@ class @Admin
e.preventDefault()
$('.js-toggle-colors-container').toggle()
- $('input#broadcast_message_color').on 'input', ->
- previewColor = $(@).val()
- $('div.broadcast-message-preview').css('background-color', previewColor)
-
- $('input#broadcast_message_font').on 'input', ->
- previewColor = $(@).val()
- $('div.broadcast-message-preview').css('color', previewColor)
-
- $('textarea#broadcast_message_message').on 'input', ->
- previewMessage = $(@).val()
- previewMessage = "Your message here" if previewMessage.trim() == ''
- $('div.broadcast-message-preview span').text(previewMessage)
-
$('.log-tabs a').click (e) ->
e.preventDefault()
$(this).tab('show')
diff --git a/app/assets/javascripts/api.js.coffee b/app/assets/javascripts/api.js.coffee
index 746fa3cea8..3e0fdb3f79 100644
--- a/app/assets/javascripts/api.js.coffee
+++ b/app/assets/javascripts/api.js.coffee
@@ -47,7 +47,7 @@
callback(namespaces)
# Return projects list. Filtered by query
- projects: (query, callback) ->
+ projects: (query, order, callback) ->
url = Api.buildUrl(Api.projects_path)
$.ajax(
@@ -55,6 +55,7 @@
data:
private_token: gon.api_token
search: query
+ order_by: order
per_page: 20
dataType: "json"
).done (projects) ->
diff --git a/app/assets/javascripts/application.js.coffee b/app/assets/javascripts/application.js.coffee
index c095e5ae2b..367bd098bf 100644
--- a/app/assets/javascripts/application.js.coffee
+++ b/app/assets/javascripts/application.js.coffee
@@ -5,7 +5,10 @@
# the compiled file.
#
#= require jquery
-#= require jquery-ui
+#= require jquery-ui/autocomplete
+#= require jquery-ui/datepicker
+#= require jquery-ui/effect-highlight
+#= require jquery-ui/sortable
#= require jquery_ujs
#= require jquery.cookie
#= require jquery.endless-scroll
@@ -21,9 +24,9 @@
#= require bootstrap
#= require select2
#= require raphael
-#= require g.raphael-min
-#= require g.bar-min
-#= require chart-lib.min
+#= require g.raphael
+#= require g.bar
+#= require Chart
#= require branch-graph
#= require ace/ace
#= require ace/ext-searchbox
@@ -38,9 +41,9 @@
#= require shortcuts_dashboard_navigation
#= require shortcuts_issuable
#= require shortcuts_network
-#= require jquery.nicescroll.min
+#= require jquery.nicescroll
#= require_tree .
-#= require fuzzaldrin-plus.min
+#= require fuzzaldrin-plus
window.slugify = (text) ->
text.replace(/[^-a-zA-Z0-9]+/g, '_').toLowerCase()
@@ -203,4 +206,94 @@ $ ->
form = btn.closest("form")
new ConfirmDangerModal(form, text)
+ $('input[type="search"]').each ->
+ $this = $(this)
+ $this.attr 'value', $this.val()
+ return
+
+ $(document)
+ .off 'keyup', 'input[type="search"]'
+ .on 'keyup', 'input[type="search"]' , (e) ->
+ $this = $(this)
+ $this.attr 'value', $this.val()
+
+ $(document)
+ .off 'breakpoint:change'
+ .on 'breakpoint:change', (e, breakpoint) ->
+ if breakpoint is 'sm' or breakpoint is 'xs'
+ $gutterIcon = $('.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) ->
+ e.preventDefault()
+ $this = $(this)
+ $thisIcon = $this.find 'i'
+ if $thisIcon.hasClass('fa-angle-double-right')
+ $thisIcon
+ .removeClass('fa-angle-double-right')
+ .addClass('fa-angle-double-left')
+ $this
+ .closest('aside')
+ .removeClass('right-sidebar-expanded')
+ .addClass('right-sidebar-collapsed')
+ $('.page-with-sidebar')
+ .removeClass('right-sidebar-expanded')
+ .addClass('right-sidebar-collapsed')
+ else
+ $thisIcon
+ .removeClass('fa-angle-double-left')
+ .addClass('fa-angle-double-right')
+ $this
+ .closest('aside')
+ .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: '/' })
+
+ bootstrapBreakpoint = undefined;
+ checkBootstrapBreakpoints = ->
+ if $('.device-xs').is(':visible')
+ bootstrapBreakpoint = "xs"
+ else if $('.device-sm').is(':visible')
+ bootstrapBreakpoint = "sm"
+ else if $('.device-md').is(':visible')
+ bootstrapBreakpoint = "md"
+ else if $('.device-lg').is(':visible')
+ bootstrapBreakpoint = "lg"
+
+ setBootstrapBreakpoints = ->
+ if $('.device-xs').length
+ return
+
+ $("body")
+ .append('
'+
+ '
'+
+ '
'+
+ '
')
+ checkBootstrapBreakpoints()
+
+ fitSidebarForSize = ->
+ oldBootstrapBreakpoint = bootstrapBreakpoint
+ checkBootstrapBreakpoints()
+ if bootstrapBreakpoint != oldBootstrapBreakpoint
+ $(document).trigger('breakpoint:change', [bootstrapBreakpoint])
+
+ checkInitialSidebarSize = ->
+ if bootstrapBreakpoint is "xs" or "sm"
+ $(document).trigger('breakpoint:change', [bootstrapBreakpoint])
+
+ $(window)
+ .off "resize"
+ .on "resize", (e) ->
+ fitSidebarForSize()
+
+ setBootstrapBreakpoints()
+ checkInitialSidebarSize()
new Aside()
diff --git a/app/assets/javascripts/awards_handler.coffee b/app/assets/javascripts/awards_handler.coffee
index 1ef31c7700..360acb864f 100644
--- a/app/assets/javascripts/awards_handler.coffee
+++ b/app/assets/javascripts/awards_handler.coffee
@@ -4,6 +4,7 @@ class @AwardsHandler
event.stopPropagation()
event.preventDefault()
$(".emoji-menu").show()
+ $("#emoji_search").focus()
$("html").on 'click', (event) ->
if !$(event.target).closest(".emoji-menu").length
@@ -48,10 +49,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/broadcast_message.js.coffee b/app/assets/javascripts/broadcast_message.js.coffee
new file mode 100644
index 0000000000..a38a329c4c
--- /dev/null
+++ b/app/assets/javascripts/broadcast_message.js.coffee
@@ -0,0 +1,22 @@
+$ ->
+ $('input#broadcast_message_color').on 'input', ->
+ previewColor = $(@).val()
+ $('div.broadcast-message-preview').css('background-color', previewColor)
+
+ $('input#broadcast_message_font').on 'input', ->
+ previewColor = $(@).val()
+ $('div.broadcast-message-preview').css('color', previewColor)
+
+ previewPath = $('textarea#broadcast_message_message').data('preview-path')
+
+ $('textarea#broadcast_message_message').on 'input', ->
+ message = $(@).val()
+
+ if message == ''
+ $('.js-broadcast-message-preview').text("Your message here")
+ else
+ $.ajax(
+ url: previewPath
+ type: "POST"
+ data: { broadcast_message: { message: message } }
+ )
diff --git a/app/assets/javascripts/dashboard.js.coffee b/app/assets/javascripts/dashboard.js.coffee
index 00ee503ff1..62143e66cf 100644
--- a/app/assets/javascripts/dashboard.js.coffee
+++ b/app/assets/javascripts/dashboard.js.coffee
@@ -1,3 +1,31 @@
-class @Dashboard
- constructor: ->
- new ProjectsList()
+@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 2cdf01d874..67a92d822e 100644
--- a/app/assets/javascripts/dispatcher.js.coffee
+++ b/app/assets/javascripts/dispatcher.js.coffee
@@ -16,6 +16,8 @@ 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()
@@ -58,7 +60,7 @@ class Dispatcher
shortcut_handler = new ShortcutsNavigation()
MergeRequests.init()
when 'dashboard:show', 'root:show'
- new Dashboard()
+ Dashboard.init()
when 'dashboard:activity'
new Activities()
when 'dashboard:projects:starred'
@@ -86,6 +88,7 @@ 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
diff --git a/app/assets/javascripts/dropzone_input.js.coffee b/app/assets/javascripts/dropzone_input.js.coffee
index c714c0fa93..b502131a99 100644
--- a/app/assets/javascripts/dropzone_input.js.coffee
+++ b/app/assets/javascripts/dropzone_input.js.coffee
@@ -65,8 +65,7 @@ class @DropzoneInput
return
success: (header, response) ->
- child = $(dropzone[0]).children("textarea")
- $(child).val $(child).val() + response.link.markdown + "\n"
+ pasteText response.link.markdown
return
error: (temp, errorMessage) ->
@@ -128,6 +127,7 @@ class @DropzoneInput
beforeSelection = $(child).val().substring 0, caretStart
afterSelection = $(child).val().substring caretEnd, textEnd
$(child).val beforeSelection + text + afterSelection
+ child.get(0).setSelectionRange caretStart + text.length, caretEnd + text.length
form_textarea.trigger "input"
getFilename = (e) ->
diff --git a/app/assets/javascripts/issuable_context.js.coffee b/app/assets/javascripts/issuable_context.js.coffee
index 02232698bc..e52b73f94f 100644
--- a/app/assets/javascripts/issuable_context.js.coffee
+++ b/app/assets/javascripts/issuable_context.js.coffee
@@ -10,20 +10,10 @@ class @IssuableContext
$(".issuable-sidebar .inline-update").on "change", ".js-assignee", ->
$(this).submit()
- $('.issuable-details').waitForImages ->
- $('.issuable-affix').on 'affix.bs.affix', ->
- $(@).width($(@).outerWidth())
- .on 'affixed-top.bs.affix affixed-bottom.bs.affix', ->
- $(@).width('')
-
- $('.issuable-affix').affix offset:
- top: ->
- @top = ($('.issuable-affix').offset().top - 70)
- bottom: ->
- @bottom = $('.footer').outerHeight(true)
-
- $(".edit-link").click (e) ->
+ $(document).on "click",".edit-link", (e) ->
block = $(@).parents('.block')
block.find('.selectbox').show()
block.find('.value').hide()
block.find('.js-select2').select2("open")
+
+ $(".right-sidebar").niceScroll()
diff --git a/app/assets/javascripts/issue.js.coffee b/app/assets/javascripts/issue.js.coffee
index cbc70cd846..d663e34871 100644
--- a/app/assets/javascripts/issue.js.coffee
+++ b/app/assets/javascripts/issue.js.coffee
@@ -50,6 +50,7 @@ class @Issue
new Flash(issueFailMessage, 'alert')
success: (data, textStatus, jqXHR) ->
if data.saved
+ $(document).trigger('issuable:change');
if isClose
$('a.btn-close').addClass('hidden')
$('a.btn-reopen').removeClass('hidden')
diff --git a/app/assets/javascripts/logo.js.coffee b/app/assets/javascripts/logo.js.coffee
index a5879c8b79..35b2fbbba0 100644
--- a/app/assets/javascripts/logo.js.coffee
+++ b/app/assets/javascripts/logo.js.coffee
@@ -42,3 +42,9 @@ work = ->
$(document).on('page:fetch', start)
$(document).on('page:change', stop)
+
+$ ->
+ # Make logo clickable as part of a workaround for Safari visited
+ # link behaviour (See !2690).
+ $('#logo').on 'click', ->
+ $('#js-shortcuts-home').get(0).click()
diff --git a/app/assets/javascripts/merge_request_tabs.js.coffee b/app/assets/javascripts/merge_request_tabs.js.coffee
index b10e1db7f3..6f569f9e1a 100644
--- a/app/assets/javascripts/merge_request_tabs.js.coffee
+++ b/app/assets/javascripts/merge_request_tabs.js.coffee
@@ -146,6 +146,7 @@ class @MergeRequestTabs
success: (data) =>
document.querySelector("div#diffs").innerHTML = data.html
$('div#diffs .js-syntax-highlight').syntaxHighlight()
+ @expandViewContainer() if @diffViewType() is 'parallel'
@diffsLoaded = true
@scrollToElement("#diffs")
@@ -177,3 +178,10 @@ 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')
diff --git a/app/assets/javascripts/milestone.js.coffee b/app/assets/javascripts/milestone.js.coffee
index d644d50b66..e6d8518bec 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)
@@ -95,10 +105,22 @@ class @Milestone
).disableSelection()
bindMergeRequestSorting: ->
+ $('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()
+
$("#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 8866d81c92..3347ab65c9 100644
--- a/app/assets/javascripts/notes.js.coffee
+++ b/app/assets/javascripts/notes.js.coffee
@@ -15,6 +15,8 @@ class @Notes
@last_fetched_at = last_fetched_at
@view = view
@noteable_url = document.URL
+ @notesCountBadge ||= $(".issuable-details").find(".notes-tab .badge")
+
@initRefresh()
@setupMainTargetNoteForm()
@cleanBinding()
@@ -62,6 +64,9 @@ class @Notes
# fetch notes when tab becomes visible
$(document).on "visibilitychange", @visibilityChange
+ # when issue status changes, we need to refresh data
+ $(document).on "issuable:change", @refresh
+
cleanBinding: ->
$(document).off "ajax:success", ".js-main-target-form"
$(document).off "ajax:success", ".js-discussion-note-form"
@@ -89,7 +94,7 @@ class @Notes
, 15000
refresh: ->
- unless document.hidden or (@noteable_url != document.URL)
+ if not document.hidden and document.URL.indexOf(@noteable_url) is 0
@getContent()
getContent: ->
@@ -101,7 +106,10 @@ class @Notes
notes = data.notes
@last_fetched_at = data.last_fetched_at
$.each notes, (i, note) =>
- @renderNote(note)
+ if note.discussion_with_diff_html?
+ @renderDiscussionNote(note)
+ else
+ @renderNote(note)
###
@@ -116,19 +124,22 @@ class @Notes
flash.pinTo('.header-content')
return
- # render note if it not present in loaded list
- # or skip if rendered
- if @isNewNote(note) && !note.award
- @note_ids.push(note.id)
- $('ul.main-notes-list').
- append(note.html).
- syntaxHighlight()
- @initTaskList()
-
if note.award
awards_handler.addAwardToEmojiBar(note.note)
awards_handler.scrollToAwards()
+ # render note if it not present in loaded list
+ # or skip if rendered
+ else if @isNewNote(note)
+ @note_ids.push(note.id)
+
+ $('ul.main-notes-list')
+ .append(note.html)
+ .syntaxHighlight()
+ @initTaskList()
+ @updateNotesCount(1)
+
+
###
Check if note does not exists on page
###
@@ -144,34 +155,39 @@ class @Notes
Note: for rendering inline notes use renderDiscussionNote
###
renderDiscussionNote: (note) ->
+ return unless @isNewNote(note)
+
@note_ids.push(note.id)
- form = $("form[rel='" + note.discussion_id + "']")
+ form = $("#new-discussion-note-form-#{note.discussion_id}")
row = form.closest("tr")
note_html = $(note.html)
note_html.syntaxHighlight()
# is this the first note of discussion?
- if row.is(".js-temp-notes-holder")
+ discussionContainer = $(".notes[data-discussion-id='" + note.discussion_id + "']")
+ if discussionContainer.length is 0
# insert the note and the reply button after the temp row
row.after note.discussion_html
# remove the note (will be added again below)
row.next().find(".note").remove()
+ # Before that, the container didn't exist
+ discussionContainer = $(".notes[data-discussion-id='" + note.discussion_id + "']")
+
# Add note to 'Changes' page discussions
- $(".notes[rel='" + note.discussion_id + "']").append note_html
+ discussionContainer.append note_html
# Init discussion on 'Discussion' page if it is merge request page
- if $('body').attr('data-page').indexOf('projects:merge_request') == 0
- discussion_html = $(note.discussion_with_diff_html)
- discussion_html.syntaxHighlight()
- $('ul.main-notes-list').append(discussion_html)
+ if $('body').attr('data-page').indexOf('projects:merge_request') is 0
+ $('ul.main-notes-list')
+ .append(note.discussion_with_diff_html)
+ .syntaxHighlight()
else
# append new note to all matching discussions
- $(".notes[rel='" + note.discussion_id + "']").append note_html
+ discussionContainer.append note_html
- # cleanup after successfully creating a diff/discussion note
- @removeDiscussionNoteForm(form)
+ @updateNotesCount(1)
###
Called in response the main target form has been successfully submitted.
@@ -278,6 +294,9 @@ class @Notes
addDiscussionNote: (xhr, note, status) =>
@renderDiscussionNote(note)
+ # cleanup after successfully creating a diff/discussion note
+ @removeDiscussionNoteForm($("#new-discussion-note-form-#{note.discussion_id}"))
+
###
Called in response to the edit note form being submitted
@@ -320,6 +339,7 @@ class @Notes
form.show()
textarea = form.find("textarea")
textarea.focus()
+ autosize(textarea)
# HACK (rspeicher/DouweM): Work around a Chrome 43 bug(?).
# The textarea has the correct value, Chrome just won't show it unless we
@@ -348,30 +368,32 @@ class @Notes
Removes the actual note from view.
Removes the whole discussion if the last note is being removed.
###
- removeNote: ->
- note = $(this).closest(".note")
- note_id = note.attr('id')
+ removeNote: (e) =>
+ noteId = $(e.currentTarget)
+ .closest(".note")
+ .attr("id")
- $('.note[id="' + note_id + '"]').each ->
- note = $(this)
+ # A same note appears in the "Discussion" and in the "Changes" tab, we have
+ # to remove all. Using $(".note[id='noteId']") ensure we get all the notes,
+ # where $("#noteId") would return only one.
+ $(".note[id='#{noteId}']").each (i, el) =>
+ note = $(el)
notes = note.closest(".notes")
- count = notes.closest(".issuable-details").find(".notes-tab .badge")
# check if this is the last note for this line
if notes.find(".note").length is 1
- # for discussions
- notes.closest(".discussion").remove()
+ # "Discussions" tab
+ notes.closest(".timeline-entry").remove()
- # for diff lines
+ # "Changes" tab / commit view
notes.closest("tr").remove()
- # update notes count
- oldNum = parseInt(count.text())
- count.text(oldNum - 1)
-
note.remove()
+ # Decrement the "Discussions" counter only once
+ @updateNotesCount(-1)
+
###
Called in response to clicking the delete attachment link
@@ -411,7 +433,7 @@ class @Notes
###
setupDiscussionNoteForm: (dataHolder, form) =>
# setup note target
- form.attr "rel", dataHolder.data("discussionId")
+ form.attr 'id', "new-discussion-note-form-#{dataHolder.data("discussionId")}"
form.find("#line_type").val dataHolder.data("lineType")
form.find("#note_commit_id").val dataHolder.data("commitId")
form.find("#note_line_code").val dataHolder.data("lineCode")
@@ -541,3 +563,6 @@ class @Notes
updateTaskList: ->
$('form', this).submit()
+
+ updateNotesCount: (updateCount) ->
+ @notesCountBadge.text(parseInt(@notesCountBadge.text()) + updateCount)
diff --git a/app/assets/javascripts/project.js.coffee b/app/assets/javascripts/project.js.coffee
index d7a658f8fa..76bc4ff42a 100644
--- a/app/assets/javascripts/project.js.coffee
+++ b/app/assets/javascripts/project.js.coffee
@@ -50,3 +50,19 @@ class @Project
$('#notifications-button').empty().append(" " + label + " ")
$(@).parents('ul').find('li.active').removeClass 'active'
$(@).parent().addClass 'active'
+
+ @projectSelectDropdown()
+
+ projectSelectDropdown: ->
+ new ProjectSelect()
+
+ $('.project-item-select').on 'click', (e) =>
+ @changeProject $(e.currentTarget).val()
+
+ $('.js-projects-dropdown-toggle').on 'click', (e) ->
+ e.preventDefault()
+
+ $('.js-projects-dropdown').select2('open')
+
+ changeProject: (url) ->
+ window.location = url
diff --git a/app/assets/javascripts/project_select.js.coffee b/app/assets/javascripts/project_select.js.coffee
index 0ae274f336..be8ab9b428 100644
--- a/app/assets/javascripts/project_select.js.coffee
+++ b/app/assets/javascripts/project_select.js.coffee
@@ -3,6 +3,7 @@ class @ProjectSelect
$('.ajax-project-select').each (i, select) ->
@groupId = $(select).data('group-id')
@includeGroups = $(select).data('include-groups')
+ @orderBy = $(select).data('order-by') || 'id'
placeholder = "Search for project"
placeholder += " or group" if @includeGroups
@@ -28,7 +29,7 @@ class @ProjectSelect
if @groupId
Api.groupProjects @groupId, query.term, projectsCallback
else
- Api.projects query.term, projectsCallback
+ Api.projects query.term, @orderBy, projectsCallback
id: (project) ->
project.web_url
diff --git a/app/assets/javascripts/projects_list.js.coffee b/app/assets/javascripts/projects_list.js.coffee
index f2887af190..a57c37e070 100644
--- a/app/assets/javascripts/projects_list.js.coffee
+++ b/app/assets/javascripts/projects_list.js.coffee
@@ -2,23 +2,31 @@ class @ProjectsList
constructor: ->
$(".projects-list .js-expand").on 'click', (e) ->
e.preventDefault()
- list = $(this).closest('.projects-list')
- list.find("li").show()
- list.find("li.bottom").hide()
+ $projectsList = $(this).closest('.projects-list')
+ ProjectsList.showPagination($projectsList)
+ $projectsList.find('li.bottom').hide()
- $(".projects-list-filter").keyup ->
- terms = $(this).val()
- uiBox = $('div.projects-list-holder')
- if terms == "" || terms == undefined
- uiBox.find("ul.projects-list li").show()
- else
- uiBox.find("ul.projects-list li").each (index) ->
- name = $(this).find("span.filter-title").text()
+ $("#filter_projects").on 'keyup', ->
+ ProjectsList.filter_results($("#filter_projects"))
- if name.toLowerCase().search(terms.toLowerCase()) == -1
- $(this).hide()
- else
- $(this).show()
- uiBox.find("ul.projects-list li.bottom").hide()
+ @showPagination: ($projectsList) ->
+ $projectsList.find('li').show()
+ $('.gl-pagination').show()
+ @filter_results: ($element) ->
+ terms = $element.val()
+ filterSelector = $element.data('filter-selector') || 'span.filter-title'
+ $projectsList = $('.projects-list')
+ if not terms
+ ProjectsList.showPagination($projectsList)
+ else
+ $projectsList.find('li').each (index) ->
+ $this = $(this)
+ name = $this.find(filterSelector).text()
+
+ if name.toLowerCase().indexOf(terms.toLowerCase()) == -1
+ $this.hide()
+ else
+ $this.show()
+ $('.gl-pagination').hide()
diff --git a/app/assets/javascripts/shortcuts.js.coffee b/app/assets/javascripts/shortcuts.js.coffee
index f141fb69c3..9c7c2474aa 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 bb53219468..cefa1857d7 100644
--- a/app/assets/javascripts/shortcuts_issuable.coffee
+++ b/app/assets/javascripts/shortcuts_issuable.coffee
@@ -5,23 +5,42 @@ class @ShortcutsIssuable extends ShortcutsNavigation
constructor: (isMergeRequest) ->
super()
Mousetrap.bind('a', ->
- $('.js-assignee').select2('open')
+ $('.block.assignee .edit-link').trigger('click')
return false
)
Mousetrap.bind('m', ->
- $('.js-milestone').select2('open')
+ $('.block.milestone .edit-link').trigger('click')
return false
)
Mousetrap.bind('r', =>
@replyWithSelectedText()
return false
)
+ Mousetrap.bind('j', =>
+ @prevIssue()
+ return false
+ )
+ Mousetrap.bind('k', =>
+ @nextIssue()
+ return false
+ )
+
if isMergeRequest
@enabledHelp.push('.hidden-shortcut.merge_requests')
else
@enabledHelp.push('.hidden-shortcut.issues')
+ prevIssue: ->
+ $prevBtn = $('.prev-btn')
+ if not $prevBtn.hasClass('disabled')
+ Turbolinks.visit($prevBtn.attr('href'))
+
+ nextIssue: ->
+ $nextBtn = $('.next-btn')
+ if not $nextBtn.hasClass('disabled')
+ Turbolinks.visit($nextBtn.attr('href'))
+
replyWithSelectedText: ->
if window.getSelection
selected = window.getSelection().toString()
diff --git a/app/assets/javascripts/sidebar.js.coffee b/app/assets/javascripts/sidebar.js.coffee
index ae59480af9..cff309c597 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/wikis.js.coffee b/app/assets/javascripts/wikis.js.coffee
index 19420f4246..1ee827f1fa 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/framework/avatar.scss b/app/assets/stylesheets/framework/avatar.scss
index 36e582d485..b7ffa3e6ff 100644
--- a/app/assets/stylesheets/framework/avatar.scss
+++ b/app/assets/stylesheets/framework/avatar.scss
@@ -24,6 +24,7 @@
&.s26 { width: 26px; height: 26px; margin-right: 8px; }
&.s32 { width: 32px; height: 32px; margin-right: 10px; }
&.s36 { width: 36px; height: 36px; margin-right: 10px; }
+ &.s40 { width: 40px; height: 40px; margin-right: 10px; }
&.s46 { width: 46px; height: 46px; margin-right: 15px; }
&.s48 { width: 48px; height: 48px; margin-right: 10px; }
&.s60 { width: 60px; height: 60px; margin-right: 12px; }
@@ -40,7 +41,8 @@
&.s16 { font-size: 12px; line-height: 1.33; }
&.s24 { font-size: 14px; line-height: 1.8; }
&.s26 { font-size: 20px; line-height: 1.33; }
- &.s32 { font-size: 22px; line-height: 32px; }
+ &.s32 { font-size: 20px; line-height: 32px; }
+ &.s40 { font-size: 16px; line-height: 40px; }
&.s60 { font-size: 32px; line-height: 60px; }
&.s90 { font-size: 36px; line-height: 90px; }
&.s110 { font-size: 40px; line-height: 112px; font-weight: 300; }
diff --git a/app/assets/stylesheets/framework/blocks.scss b/app/assets/stylesheets/framework/blocks.scss
index d0f5d33bf4..bd89cc7dc1 100644
--- a/app/assets/stylesheets/framework/blocks.scss
+++ b/app/assets/stylesheets/framework/blocks.scss
@@ -146,6 +146,10 @@
border-bottom: 1px solid $border-color;
&.oneline-block {
- line-height: 42px;
+ line-height: 36px;
+ }
+
+ > .controls {
+ float: right;
}
}
diff --git a/app/assets/stylesheets/framework/buttons.scss b/app/assets/stylesheets/framework/buttons.scss
index c99292c3f8..5f193fa743 100644
--- a/app/assets/stylesheets/framework/buttons.scss
+++ b/app/assets/stylesheets/framework/buttons.scss
@@ -2,7 +2,7 @@
@include border-radius(3px);
font-size: $gl-font-size;
font-weight: 500;
- padding: $gl-vert-padding $gl-padding;
+ padding: $gl-vert-padding $gl-btn-padding;
&:focus,
&:active {
@@ -82,8 +82,7 @@
&.btn-success,
&.btn-new,
&.btn-create,
- &.btn-save,
- &.btn-green {
+ &.btn-save {
@include btn-green;
}
@@ -159,7 +158,6 @@
.input-group-btn {
.btn {
- @include btn-gray;
@include btn-middle;
&:hover {
@@ -186,8 +184,4 @@
border: 1px solid #c6cacf !important;
background-color: #e4e7ed !important;
}
-
- .btn-green {
- @include btn-green
- }
}
diff --git a/app/assets/stylesheets/framework/common.scss b/app/assets/stylesheets/framework/common.scss
index 6ea2219073..ea56d9e12a 100644
--- a/app/assets/stylesheets/framework/common.scss
+++ b/app/assets/stylesheets/framework/common.scss
@@ -376,11 +376,11 @@ table {
margin-bottom: $gl-padding;
}
-.new-project-item-select-holder {
+.project-item-select-holder {
display: inline-block;
position: relative;
- .new-project-item-select {
+ .project-item-select {
position: absolute;
top: 0;
right: 0;
diff --git a/app/assets/stylesheets/framework/files.scss b/app/assets/stylesheets/framework/files.scss
index 00cb756b37..07907e6e5a 100644
--- a/app/assets/stylesheets/framework/files.scss
+++ b/app/assets/stylesheets/framework/files.scss
@@ -36,6 +36,20 @@
}
}
+ .filename {
+ &.old {
+ span.idiff {
+ background-color: #f8cbcb;
+ }
+ }
+
+ &.new {
+ span.idiff {
+ background-color: #a6f3a6;
+ }
+ }
+ }
+
.left-options {
margin-top: -3px;
}
@@ -144,7 +158,7 @@
}
&:hover {
- background: $hover;
+ background: $row-hover;
}
}
}
@@ -158,3 +172,15 @@
}
}
}
+
+span.idiff {
+ &.left {
+ border-top-left-radius: 2px;
+ border-bottom-left-radius: 2px;
+ }
+
+ &.right {
+ border-top-right-radius: 2px;
+ border-bottom-right-radius: 2px;
+ }
+}
diff --git a/app/assets/stylesheets/framework/forms.scss b/app/assets/stylesheets/framework/forms.scss
index 4dab806d50..d097e4d32f 100644
--- a/app/assets/stylesheets/framework/forms.scss
+++ b/app/assets/stylesheets/framework/forms.scss
@@ -2,11 +2,42 @@ textarea {
resize: vertical;
}
-input[type='search'].search-text-input {
- background-image: image-url("icon-search.png");
+input {
+ border-radius: $border-radius-base;
+}
+
+input[type='search'] {
+ background-color: white;
+ padding-left: 10px;
+}
+
+input[type='search'].search-input {
background-repeat: no-repeat;
background-position: 10px;
- padding-left: 25px;
+ background-size: 16px;
+ background-position-x: 30%;
+ padding-left: 10px;
+ background-color: $gray-light;
+
+ &.search-input[value=""] {
+ background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABwAAAAcCAYAAAByDd+UAAAFu0lEQVRIia1WTahkVxH+quqce7vf6zdvJpHoIlkYJ2SiJiIokmQjgoGgIAaEIYuYXWICgojiwkmC4taFwhjcyIDusogEIwwiSSCKPwsdwzAg0SjJ9Izzk5n3+nXfe8+pqizOvd395scfsJqi6dPnnDr11Vc/NJ1OwUTosqJLCmYCHCAC2mSHs+ojZv6AO46Y+20AhIneJsafhPhXVZSXDk7qi+aOLhtQNuBmQtcarAKjTXpn2+l3u2yPunvZSABRucjcAV/eMZuM48/Go/g1d19kc4wq+e8MZjWkbI/P5t2P3RFFbv7SQdyBlBUx8N8OTuqjMcof+N94yMPrY2DMm/ytnb32J0QrY+6AqsHM4Q64O9SKDmerKDD3Oy/tNL9vk342CC8RuU6n0ymCMHb22scu7zQngtASOjUHE1BX4UUAv4b7Ow6qiXCXuz/UdvogAAweDY943/b4cAz0ZlYHXeMsnT07RVb7wMUr8ykI4H5HVkMd5Rcb4/jNURVOL5qErAaAUUdCCIJ5kx5q2nw8m39ImEAAsjpE6PStB0YfMcd1wqqG3Xn7A3PfZyyKnNjaqD4fmE/fCNKshirIyY1xvI+Av6g5QIAIIWX7cJPssboSiBBEeKmsZne0Sb8kzAUWNYyq8NvbDo0fZ6beqxuLmqOOMr/lwOh+YXpXtbjERGja9JyZ9+HxpXKb9Gj5oywRESbj+Cj1ENG1QViTGBl1FbC1We1tbVRfHWIoQkhqH9xbpE92XUbb6VJZ1R4crjRz1JWcDMJvLdoMcyAEhjuwHo8Bfndg3mbszhOY+adVlMtD3po51OwzIQiEaams7oeJhxRw1FFOVpFRRUYIhMBAFRnjOsC8IFHHUA4TQQhgAqpAiIFfGbxkIqj54ayGbL7UoOqHCniAEKHLNr26l+D9wQJzeUwMAnfHvEnLECzZRwRV++d60ptjW9VLZeolEJG6GwCCE0CFVNB+Ay0NEqoQYG4YYFu7B8IEVRt3uRzy/osIoLV9QZimWXGHUMFdmI6M64DUF2Je88R9VZqCSP+QlcF5k+4tCzSsXaqjINuK6UyE0+s/mk6/qFq8oAIL9pqMLhkGsNrOyoOIlszust3aJv0U9+kFdwjTGwWl1YdF+KWlQSZ0Se/psj8yGVdg5tJyfH96EBWmLtoEMwMzMFt031NzGWLLzKhC+KV7H5ZeeaMOPxemma2x68puc0LN3+/u6LJiePS6MKHvn4wu6cPzJj0hsioeMfDrEvjv5r6W9gBvjKJujuKzQ0URIZj75NylvT+mbHfXQa4rwAMaVRTMm/SFyzvNy0yF6+4AM+1ubcSnqkAIUjQKl1RKSbE5jt+vovx1MBqF0WW7/d1Z80ab9BtmuJ3Xk5cJKds9TZt/uLPXvtiTrQ+dIwqfAejUvM1os6FNikXKUHfQ+ekUsXT5u85enJ0CaBSkkGEo1syUQ+DfMdE/4GA1uzupf9zdbzhOmLsF4efHVXjaHHAzmDtGdQRd/Nc5wAEJjNki3XfhyvwVNz80xANrht3LsENY9cBBdN1L9GUyyvFRFZ42t75sBvCQRykbRlU4tT2pPxoCvzx09d4GmPs200M6wKdWSDGK8mppYSWdhAlt0qeaLv+IadXU9/Evq4FAZ8ej+LmtcTxaRX4NWI0Uag5Vg1p5MYg8BnlhXIdPHDow+vTWZvVMVttXDLqkTzZdPj6Qii6cP1cSvIdl3iQkNYyi9HH0I22y+93tY3DcQkTZgQtM+POoCr8x97eylkmtrgKuztrvXJ21x/aNKuqIkZ/fntRfCdcTfhUTAIhRzoDojJD0aSNLLwMzmpT7+JaLtyf1MwDo6qz9djFaUq3t9MlFmy/c1OCSceY9fMsVaL9mvH9ocXdkdWxv1scAePG0THAhMOaLdOw/Gvxfxb1w4eCapyIENUcV5M3/u8FitAxZ25P6GAHT3UX39Srw+QOb1ZffA98Dl2Wy1BYkAAAAAElFTkSuQmCC');
+ }
+
+ &.search-input::-webkit-input-placeholder {
+ text-align: center;
+ }
+
+ &.search-input:-moz-placeholder { /* Firefox 18- */
+ text-align: center;
+ }
+
+ &.search-input::-moz-placeholder { /* Firefox 19+ */
+ text-align: center;
+ }
+
+ &.search-input:-ms-input-placeholder {
+ text-align: center;
+ }
}
input[type='text'].danger {
@@ -74,6 +105,7 @@ label {
.form-control {
@include box-shadow(none);
+ border-radius: 3px;
}
.form-control-inline {
diff --git a/app/assets/stylesheets/framework/gitlab-theme.scss b/app/assets/stylesheets/framework/gitlab-theme.scss
index 8d9a0aae56..12cef6f8ea 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 ba5e72c8c5..c2676cd1cc 100644
--- a/app/assets/stylesheets/framework/header.scss
+++ b/app/assets/stylesheets/framework/header.scss
@@ -73,7 +73,6 @@ header {
.title {
margin: 0;
- overflow: hidden;
font-size: 19px;
line-height: $header-height;
font-weight: normal;
@@ -88,6 +87,22 @@ header {
text-decoration: underline;
}
}
+
+ .dropdown-toggle-caret {
+ position: relative;
+ top: -2px;
+ width: 12px;
+ line-height: 12px;
+ margin-left: 5px;
+ font-size: 10px;
+ text-align: center;
+ cursor: pointer;
+ }
+
+ .project-item-select {
+ right: auto;
+ left: 0;
+ }
}
.navbar-collapse {
@@ -108,16 +123,10 @@ header {
.search-input {
width: 220px;
- background-image: image-url("icon-search.png");
- background-repeat: no-repeat;
- background-position: 195px;
- @include input-big;
&:focus {
@include box-shadow(none);
outline: none;
- border-color: #DDD;
- background-color: #FFF;
}
}
}
@@ -132,9 +141,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/issue_box.scss b/app/assets/stylesheets/framework/issue_box.scss
index e93dbab0c4..08dcb563dc 100644
--- a/app/assets/stylesheets/framework/issue_box.scss
+++ b/app/assets/stylesheets/framework/issue_box.scss
@@ -9,7 +9,7 @@
display: block;
float: left;
- padding: 0 $gl-padding;
+ padding: 0 $gl-btn-padding;
font-weight: normal;
margin-right: 10px;
font-size: $gl-font-size;
diff --git a/app/assets/stylesheets/framework/jquery.scss b/app/assets/stylesheets/framework/jquery.scss
index 871b808bad..0cdcd923b3 100644
--- a/app/assets/stylesheets/framework/jquery.scss
+++ b/app/assets/stylesheets/framework/jquery.scss
@@ -48,8 +48,19 @@
.ui-state-hover,
.ui-state-focus {
- border: 1px solid $hover;
- background: $hover;
+ border: 1px solid $row-hover;
+ background: $row-hover;
color: #333;
}
}
+
+.ui-sortable-handle {
+ cursor: move;
+ cursor: -webkit-grab;
+ cursor: -moz-grab;
+
+ &:active {
+ cursor: -webkit-grabbing;
+ cursor: -moz-grabbing;
+ }
+}
diff --git a/app/assets/stylesheets/framework/lists.scss b/app/assets/stylesheets/framework/lists.scss
index c6bc6fb324..354392d5ec 100644
--- a/app/assets/stylesheets/framework/lists.scss
+++ b/app/assets/stylesheets/framework/lists.scss
@@ -38,7 +38,7 @@
&.smoke { background-color: $background-color; }
&:hover {
- background: $hover;
+ background: $row-hover;
}
&:last-child {
@@ -109,7 +109,6 @@ ul.content-list {
padding: 0;
> li {
- padding: $gl-padding 0;
border-color: $table-border-color;
color: $gl-gray;
diff --git a/app/assets/stylesheets/framework/mobile.scss b/app/assets/stylesheets/framework/mobile.scss
index 0997dfc287..3bfac2ad9b 100644
--- a/app/assets/stylesheets/framework/mobile.scss
+++ b/app/assets/stylesheets/framework/mobile.scss
@@ -116,7 +116,7 @@
display: none;
}
- aside {
+ aside:not(.right-sidebar){
display: none;
}
diff --git a/app/assets/stylesheets/framework/nav.scss b/app/assets/stylesheets/framework/nav.scss
index c537d97fb2..d24faa897a 100644
--- a/app/assets/stylesheets/framework/nav.scss
+++ b/app/assets/stylesheets/framework/nav.scss
@@ -37,3 +37,102 @@
}
}
}
+
+.top-area {
+ @include clearfix;
+
+ border-bottom: 1px solid #EEE;
+
+ .nav-text {
+ padding-top: 16px;
+ padding-bottom: 11px;
+ display: inline-block;
+ width: 50%;
+ line-height: 28px;
+
+ /* Small devices (phones, tablets, 768px and lower) */
+ @media (max-width: $screen-sm-min) {
+ width: 100%;
+ }
+ }
+
+ .nav-links {
+ display: inline-block;
+ width: 50%;
+ margin-bottom: 0px;
+ border-bottom: none;
+
+ /* Small devices (phones, tablets, 768px and lower) */
+ @media (max-width: $screen-sm-min) {
+ width: 100%;
+ }
+ }
+
+ .nav-controls {
+ width: 50%;
+ display: inline-block;
+ float: right;
+ text-align: right;
+ padding: 11px 0;
+ margin-bottom: 0px;
+
+ > .dropdown {
+ margin-right: $gl-padding-top;
+ display: inline-block;
+ }
+
+ > .btn {
+ margin-right: $gl-padding-top;
+ display: inline-block;
+
+ &:last-child {
+ margin-right: 0;
+ }
+ }
+
+ > .btn-grouped {
+ float: none;
+ }
+
+ > form {
+ display: inline-block;
+ }
+
+ input {
+ height: 34px;
+ display: inline-block;
+ position: relative;
+ top: 1px;
+ margin-right: $gl-padding-top;
+
+ /* Medium devices (desktops, 992px and up) */
+ @media (min-width: $screen-md-min) { width: 200px; }
+
+ /* Large devices (large desktops, 1200px and up) */
+ @media (min-width: $screen-lg-min) { width: 250px; }
+
+ &.input-short {
+ /* Medium devices (desktops, 992px and up) */
+ @media (min-width: $screen-md-min) { width: 170px; }
+
+ /* Large devices (large desktops, 1200px and up) */
+ @media (min-width: $screen-lg-min) { width: 210px; }
+ }
+ }
+
+ /* Hide on extra small devices (phones) */
+ @media (max-width: $screen-xs-max) {
+ display: none;
+ }
+
+ /* Small devices (tablets, 768px and lower) */
+ @media (max-width: $screen-sm-max) {
+ width: 100%;
+ text-align: left;
+
+ input {
+ width: 300px;
+ }
+ }
+ }
+}
diff --git a/app/assets/stylesheets/framework/pagination.scss b/app/assets/stylesheets/framework/pagination.scss
index 2cd30491bf..b6f21fd8c9 100644
--- a/app/assets/stylesheets/framework/pagination.scss
+++ b/app/assets/stylesheets/framework/pagination.scss
@@ -1,35 +1,11 @@
.gl-pagination {
+ text-align: center;
border-top: 1px solid $border-color;
- background-color: $background-color;
- margin: -$gl-padding;
+ margin: 0;
margin-top: 0;
.pagination {
padding: 0;
- margin: 0;
- display: block;
-
- li.first,
- li.last,
- li.next,
- li.prev {
- > a {
- color: $link-color;
-
- &:hover {
- color: #fff;
- }
- }
- }
-
- li > a,
- li > span {
- border: none;
- margin: 0;
- @include border-radius(0 !important);
- padding: 13px 19px;
- border-right: 1px solid $border-color;
- }
}
}
diff --git a/app/assets/stylesheets/framework/panels.scss b/app/assets/stylesheets/framework/panels.scss
index 57b9451b26..ae7bdf14c4 100644
--- a/app/assets/stylesheets/framework/panels.scss
+++ b/app/assets/stylesheets/framework/panels.scss
@@ -2,7 +2,13 @@
margin-bottom: $gl-padding;
.panel-heading {
- padding: 7px $gl-padding;
+ padding: $gl-vert-padding $gl-padding;
+ line-height: 36px;
+
+ .controls {
+ margin-top: -2px;
+ float: right;
+ }
}
.panel-body {
@@ -14,7 +20,3 @@
}
}
}
-
-.container-blank .panel .panel-heading {
- line-height: 42px !important;
-}
diff --git a/app/assets/stylesheets/framework/sidebar.scss b/app/assets/stylesheets/framework/sidebar.scss
index 540d0b0316..de947c89c1 100644
--- a/app/assets/stylesheets/framework/sidebar.scss
+++ b/app/assets/stylesheets/framework/sidebar.scss
@@ -12,6 +12,23 @@
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 {
+ padding-right: $gutter_width;
+ }
}
.sidebar-wrapper {
@@ -70,7 +87,7 @@
width: 158px;
float: left;
margin: 0;
- margin-left: 14px;
+ margin-left: 50px;
font-size: 19px;
line-height: 41px;
font-weight: normal;
@@ -181,6 +198,10 @@
@mixin expanded-sidebar {
padding-left: $sidebar_width;
+ &.right-sidebar-collapsed {
+ padding-right: $sidebar_collapsed_width;
+ }
+
.sidebar-wrapper {
width: $sidebar_width;
@@ -203,6 +224,10 @@
@mixin collapsed-sidebar {
padding-left: $sidebar_collapsed_width;
+ &.right-sidebar-collapsed {
+ padding-right: $sidebar_collapsed_width;
+ }
+
.sidebar-wrapper {
width: $sidebar_collapsed_width;
@@ -266,26 +291,10 @@
background: #f2f6f7;
}
-@media (max-width: $screen-md-max) {
- .page-sidebar-collapsed {
- @include collapsed-sidebar;
- }
-
- .page-sidebar-expanded {
- @include collapsed-sidebar;
- }
-
- .collapse-nav {
- display: none;
- }
+.page-sidebar-collapsed {
+ @include collapsed-sidebar;
}
-@media(min-width: $screen-md-max) {
- .page-sidebar-collapsed {
- @include collapsed-sidebar;
- }
-
- .page-sidebar-expanded {
- @include expanded-sidebar;
- }
+.page-sidebar-expanded {
+ @include expanded-sidebar;
}
diff --git a/app/assets/stylesheets/framework/timeline.scss b/app/assets/stylesheets/framework/timeline.scss
index 47b843e5e3..aa244fe548 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 88072606bf..3e70924487 100644
--- a/app/assets/stylesheets/framework/tw_bootstrap.scss
+++ b/app/assets/stylesheets/framework/tw_bootstrap.scss
@@ -114,22 +114,9 @@
*
*/
-.container-blank .panel .panel-heading {
- font-size: 17px;
- line-height: 38px;
-}
-
.panel {
box-shadow: none;
- .panel-heading {
- .panel-head-actions {
- position: relative;
- top: -5px;
- float: right;
- }
- }
-
.panel-body {
form, pre {
margin: 0;
diff --git a/app/assets/stylesheets/framework/tw_bootstrap_variables.scss b/app/assets/stylesheets/framework/tw_bootstrap_variables.scss
index cd0621cdbf..b1b8295411 100644
--- a/app/assets/stylesheets/framework/tw_bootstrap_variables.scss
+++ b/app/assets/stylesheets/framework/tw_bootstrap_variables.scss
@@ -22,9 +22,9 @@ $brand-info: $gl-info;
$brand-warning: $gl-warning;
$brand-danger: $gl-danger;
-$border-radius-base: 2px !default;
-$border-radius-large: 2px !default;
-$border-radius-small: 2px !default;
+$border-radius-base: 3px !default;
+$border-radius-large: 3px !default;
+$border-radius-small: 3px !default;
//== Scaffolding
@@ -66,20 +66,20 @@ $legend-color: $text-color;
//##
$pagination-color: $gl-gray;
-$pagination-bg: $background-color;
-$pagination-border: transparent;
+$pagination-bg: #fff;
+$pagination-border: $border-color;
-$pagination-hover-color: #fff;
-$pagination-hover-bg: $brand-info;
-$pagination-hover-border: transparent;
+$pagination-hover-color: $gl-gray;
+$pagination-hover-bg: $row-hover;
+$pagination-hover-border: $border-color;
-$pagination-active-color: #fff;
-$pagination-active-bg: $brand-info;
-$pagination-active-border: transparent;
+$pagination-active-color: $blue-dark;
+$pagination-active-bg: #fff;
+$pagination-active-border: $border-color;
-$pagination-disabled-color: #fff;
-$pagination-disabled-bg: lighten($brand-info, 15%);
-$pagination-disabled-border: transparent;
+$pagination-disabled-color: #cdcdcd;
+$pagination-disabled-bg: $background-color;
+$pagination-disabled-border: $border-color;
//== Form states and alerts
diff --git a/app/assets/stylesheets/framework/typography.scss b/app/assets/stylesheets/framework/typography.scss
index 4866a17005..8d8f41287d 100644
--- a/app/assets/stylesheets/framework/typography.scss
+++ b/app/assets/stylesheets/framework/typography.scss
@@ -115,7 +115,7 @@
ul, ol {
padding: 0;
- margin: 6px 0 6px 18px !important;
+ margin: 6px 0 6px 28px !important;
}
li {
diff --git a/app/assets/stylesheets/framework/variables.scss b/app/assets/stylesheets/framework/variables.scss
index 85ecdddda7..4888854625 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;
@@ -12,6 +12,9 @@ $gl-font-size: 15px;
$list-font-size: 15px;
$sidebar_collapsed_width: 62px;
$sidebar_width: 230px;
+$gutter_collapsed_width: 62px;
+$gutter_width: 290px;
+$gutter_inner_width: 258px;
$avatar_radius: 50%;
$code_font_size: 13px;
$code_line_height: 1.5;
@@ -22,10 +25,12 @@ $header-height: 58px;
$fixed-layout-width: 1280px;
$gl-gray: #5a5a5a;
$gl-padding: 16px;
+$gl-btn-padding: 10px;
$gl-vert-padding: 6px;
$gl-padding-top:10px;
-$gl-avatar-size: 46px;
+$gl-avatar-size: 40px;
$secondary-text: #7f8fa4;
+$error-exclamation-point: #E62958;
/*
* Color schema
@@ -35,11 +40,12 @@ $white-light: #FFFFFF;
$white-normal: #ededed;
$white-dark: #ededed;
-$gray-light: #f7f7f7;
-$gray-normal: #ededed;
+$gray-light: #faf9f9;
+$gray-normal: #f5f5f5;
$gray-dark: #ededed;
+$gray-darkest: #c9c9c9;
-$green-light: #31AF64;
+$green-light: #38ae67;
$green-normal: #2FAA60;
$green-dark: #2CA05B;
@@ -51,7 +57,7 @@ $blue-medium-light: #3498CB;
$blue-medium: #2F8EBF;
$blue-medium-dark: #2D86B4;
-$orange-light: #FC6443;
+$orange-light: rgba(252, 109, 38, 0.80);
$orange-normal: #E75E40;
$orange-dark: #CE5237;
@@ -63,8 +69,8 @@ $border-white-light: #F1F2F4;
$border-white-normal: #D6DAE2;
$border-white-dark: #C6CACF;
-$border-gray-light: #d1d1d1;
-$border-gray-normal: #D6DAE2;
+$border-gray-light: rgba(0, 0, 0, 0.06);
+$border-gray-normal: rgba(0, 0, 0, 0.10);;
$border-gray-dark: #C6CACF;
$border-green-light: #2FAA60;
@@ -75,7 +81,7 @@ $border-blue-light: #2D9FD8;
$border-blue-normal: #2897CE;
$border-blue-dark: #258DC1;
-$border-orange-light: #ED5C3D;
+$border-orange-light: #fc6d26;
$border-orange-normal: #CE5237;
$border-orange-dark: #C14E35;
diff --git a/app/assets/stylesheets/pages/admin.scss b/app/assets/stylesheets/pages/admin.scss
index 144852e787..a61161810a 100644
--- a/app/assets/stylesheets/pages/admin.scss
+++ b/app/assets/stylesheets/pages/admin.scss
@@ -55,6 +55,16 @@
@extend .alert-warning;
padding: 10px;
text-align: center;
+
+ > div, p {
+ display: inline;
+ margin: 0;
+
+ a {
+ color: inherit;
+ text-decoration: underline;
+ }
+ }
}
.broadcast-message-preview {
diff --git a/app/assets/stylesheets/pages/appearances.scss b/app/assets/stylesheets/pages/appearances.scss
new file mode 100644
index 0000000000..e2070f17c3
--- /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/dashboard.scss b/app/assets/stylesheets/pages/dashboard.scss
index 25a86cd0f9..8863939914 100644
--- a/app/assets/stylesheets/pages/dashboard.scss
+++ b/app/assets/stylesheets/pages/dashboard.scss
@@ -40,10 +40,6 @@
.avatar {
@include border-radius(50%);
}
-
- .identicon {
- line-height: 46px;
- }
}
.dash-project-access-icon {
diff --git a/app/assets/stylesheets/pages/detail_page.scss b/app/assets/stylesheets/pages/detail_page.scss
index 529a43548c..d93b6ee673 100644
--- a/app/assets/stylesheets/pages/detail_page.scss
+++ b/app/assets/stylesheets/pages/detail_page.scss
@@ -12,6 +12,14 @@
.identifier {
color: #5c5d5e;
}
+
+ .issue_created_ago, .author_link {
+ white-space: nowrap;
+ }
+
+ .issue-meta {
+ margin-left: 65px
+ }
}
.detail-page-description {
diff --git a/app/assets/stylesheets/pages/diff.scss b/app/assets/stylesheets/pages/diff.scss
index 12b5aaff45..a7925e7954 100644
--- a/app/assets/stylesheets/pages/diff.scss
+++ b/app/assets/stylesheets/pages/diff.scss
@@ -56,6 +56,7 @@
width: 100%;
font-family: $monospace_font;
border: none;
+ border-collapse: separate;
margin: 0px;
padding: 0px;
.line_holder td {
diff --git a/app/assets/stylesheets/pages/emojis.scss b/app/assets/stylesheets/pages/emojis.scss
index 89a94c5a78..6c721b514f 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-icon{
- background-image: image-url("emoji.png");
- background-repeat: no-repeat;
-}
-
.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
+.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');
+ background-repeat: no-repeat;
+ height: 20px;
+ width: 20px;
+
+ @media only screen and (-webkit-min-device-pixel-ratio: 2),
+ only screen and (min--moz-device-pixel-ratio: 2),
+ only screen and (-o-min-device-pixel-ratio: 2/1),
+ only screen and (min-device-pixel-ratio: 2),
+ only screen and (min-resolution: 192dpi),
+ only screen and (min-resolution: 2dppx) {
+ background-image: image-url('emoji@2x.png');
+ background-size: 840px 820px;
+ }
+}
diff --git a/app/assets/stylesheets/pages/events.scss b/app/assets/stylesheets/pages/events.scss
index 8fa15b3574..35df9a61c8 100644
--- a/app/assets/stylesheets/pages/events.scss
+++ b/app/assets/stylesheets/pages/events.scss
@@ -4,7 +4,7 @@
*/
.event-item {
font-size: $gl-font-size;
- padding: $gl-padding 0 $gl-padding ($gl-avatar-size + 15px);
+ padding: $gl-padding-top 0 $gl-padding-top ($gl-avatar-size + $gl-padding-top);
border-bottom: 1px solid $table-border-color;
color: #7f8fa4;
@@ -16,7 +16,7 @@
.event-title,
.event-item-timestamp {
- line-height: 44px;
+ line-height: 40px;
}
}
@@ -25,7 +25,7 @@
}
.avatar {
- margin-left: -($gl-avatar-size + 15px);
+ margin-left: -($gl-avatar-size + $gl-padding-top);
}
.event-title {
@@ -41,7 +41,6 @@
margin-right: 174px;
.event-note {
- margin-top: 5px;
word-wrap: break-word;
.md {
@@ -98,8 +97,6 @@
&:last-child { border:none }
.event_commits {
- margin-top: 9px;
-
li {
&.commit {
background: transparent;
diff --git a/app/assets/stylesheets/pages/explore.scss b/app/assets/stylesheets/pages/explore.scss
index da06fe9954..9b92128624 100644
--- a/app/assets/stylesheets/pages/explore.scss
+++ b/app/assets/stylesheets/pages/explore.scss
@@ -6,11 +6,3 @@
font-size: 30px;
}
}
-
-.explore-trending-block {
- .lead {
- line-height: 32px;
- font-size: 18px;
- margin-top: 10px;
- }
-}
diff --git a/app/assets/stylesheets/pages/groups.scss b/app/assets/stylesheets/pages/groups.scss
index 263993f59a..ec6c099df5 100644
--- a/app/assets/stylesheets/pages/groups.scss
+++ b/app/assets/stylesheets/pages/groups.scss
@@ -1,5 +1,15 @@
.member-search-form {
float: left;
+
+ input[type='search'] {
+ width: 225px;
+ vertical-align: bottom;
+
+ @media (max-width: $screen-xs-max) {
+ width: 100px;
+ vertical-align: bottom;
+ }
+ }
}
.milestone-row {
@@ -11,3 +21,21 @@
height: 42px;
}
}
+
+.group-row {
+ &.no-description {
+ .group-name {
+ line-height: 44px;
+ }
+ }
+
+ .stats {
+ float: right;
+ line-height: 44px;
+ color: $gl-gray;
+
+ span {
+ margin-right: 15px;
+ }
+ }
+}
diff --git a/app/assets/stylesheets/pages/issuable.scss b/app/assets/stylesheets/pages/issuable.scss
index 977ada0ff3..b61d1f180b 100644
--- a/app/assets/stylesheets/pages/issuable.scss
+++ b/app/assets/stylesheets/pages/issuable.scss
@@ -29,21 +29,8 @@
}
}
-.project-issuable-filter {
- .controls {
- float: right;
- margin-top: 11px;
- }
-
- .nav-links {
- text-align: left;
- }
-}
-
.issuable-details {
section {
- border-right: 1px solid $border-white-light;
-
.issuable-discussion {
margin-right: 1px;
}
@@ -73,11 +60,41 @@
.block {
@include clearfix;
padding: $gl-padding 0;
- border-bottom: 1px solid #F0F0F0;
+ border-bottom: 1px solid $border-gray-light;
+ // This prevents the mess when resizing the sidebar
+ // of elements repositioning themselves..
+ width: $gutter_inner_width;
+ // --
+
+ &:first-child {
+ padding-top: 5px;
+ }
&:last-child {
border: none;
}
+
+ span {
+ margin-top: 7px;
+ display: inline-block;
+ }
+
+ .select2-container span {
+ margin-top: 0;
+ }
+
+ .issuable-count {
+
+ }
+
+ .gutter-toggle {
+ margin-left: 20px;
+ padding-left: 10px;
+
+ &:hover {
+ color: $gray-darkest;
+ }
+ }
}
.title {
@@ -133,3 +150,105 @@
margin-right: 2px;
}
}
+
+
+.right-sidebar {
+ position: fixed;
+ top: 58px;
+ bottom: 0;
+ right: 0;
+ transition: width .3s;
+ background: $gray-light;
+ padding: 10px 20px;
+
+ &.right-sidebar-expanded {
+ width: $gutter_width;
+
+ hr {
+ display: none;
+ }
+
+ .sidebar-collapsed-icon {
+ display: none;
+ }
+
+ .gutter-toggle {
+ border-left: 1px solid $border-gray-light;
+ }
+ }
+
+ .subscribe-button {
+ span {
+ margin-top: 0;
+ }
+ }
+
+ &.right-sidebar-collapsed {
+ width: $sidebar_collapsed_width;
+ padding-top: 0;
+
+ hr {
+ margin: 0;
+ color: $gray-normal;
+ border-color: $gray-normal;
+ width: 62px;
+ margin-left: -20px
+ }
+
+ .block {
+ width: $sidebar_collapsed_width - 1px;
+ margin-left: -19px;
+ padding: 15px 0 0 0;
+ border-bottom: none;
+ overflow: hidden;
+ }
+
+ .hide-collapsed {
+ display: none;
+ }
+
+ .gutter-toggle {
+ margin-left: -36px;
+ }
+
+ .sidebar-collapsed-icon {
+ display: block;
+ width: 100%;
+ text-align: center;
+ padding-bottom: 10px;
+ color: #999999;
+
+ span {
+ display: block;
+ margin-top: 0;
+ }
+
+ .btn-clipboard {
+ border: none;
+
+ &:hover {
+ background: transparent;
+ }
+
+ i {
+ color: #999999;
+ }
+ }
+ }
+ }
+
+ .btn {
+ background: $gray-normal;
+ border: 1px solid $border-gray-normal;
+ &:hover {
+ background: $gray-dark;
+ border: 1px solid $border-gray-dark;
+ }
+ }
+}
+
+.detail-page-description {
+ small {
+ color: $gray-darkest;
+ }
+}
diff --git a/app/assets/stylesheets/pages/issues.scss b/app/assets/stylesheets/pages/issues.scss
index ad92cc2281..8694bd654a 100644
--- a/app/assets/stylesheets/pages/issues.scss
+++ b/app/assets/stylesheets/pages/issues.scss
@@ -49,11 +49,6 @@
.issue-search-form {
margin: 0;
height: 24px;
-
- .issue_search {
- border: 1px solid #DDD !important;
- background-color: #f4f4f4;
- }
}
form.edit-issue {
@@ -70,10 +65,6 @@ form.edit-issue {
width: 3em;
}
-.merge-request-info {
- padding-left: 5px;
-}
-
.merge-request-status {
color: $gl-gray;
font-size: 15px;
@@ -148,4 +139,4 @@ form.edit-issue {
.issue-closed-by-widget {
color: $secondary-text;
margin-left: 52px;
-}
\ No newline at end of file
+}
diff --git a/app/assets/stylesheets/pages/labels.scss b/app/assets/stylesheets/pages/labels.scss
index d1590e42fc..1c78aafdb8 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/merge_requests.scss b/app/assets/stylesheets/pages/merge_requests.scss
index 75f2ae80a9..9a2c4b83ff 100644
--- a/app/assets/stylesheets/pages/merge_requests.scss
+++ b/app/assets/stylesheets/pages/merge_requests.scss
@@ -201,3 +201,39 @@
.mr-source-target {
line-height: 31px;
}
+
+.disabled-comment-area {
+ padding: 16px 0;
+
+ .disabled-profile {
+ width: 40px;
+ height: 40px;
+ background: $border-gray-dark;
+ border-radius: 20px;
+ display: inline-block;
+ margin-right: 10px;
+ }
+
+ .disabled-comment {
+ background: $gray-light;
+ display: inline-block;
+ vertical-align: top;
+ height: 200px;
+ border-radius: 4px;
+ border: 1px solid $border-gray-normal;
+ padding-top: 90px;
+ text-align: center;
+ right: 20px;
+ position: absolute;
+ left: 70px;
+ margin-bottom: 20px;
+
+ span {
+ color: #B2B2B2;
+
+ a {
+ color: $md-link-color;
+ }
+ }
+ }
+}
diff --git a/app/assets/stylesheets/pages/milestone.scss b/app/assets/stylesheets/pages/milestone.scss
index e80dc9e84a..d24adbf67e 100644
--- a/app/assets/stylesheets/pages/milestone.scss
+++ b/app/assets/stylesheets/pages/milestone.scss
@@ -11,3 +11,60 @@ li.milestone {
height: 6px;
}
}
+
+.milestone-content {
+ .issues-count {
+ margin-right: 17px;
+ float: right;
+ width: 105px;
+ }
+
+ .issue-row {
+ .color-label {
+ border-radius: 2px;
+ padding: 3px !important;
+ }
+
+ // 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 {
+ .issue-detail {
+ display: block;
+
+ .issue-number{
+ color: rgba(0,0,0,0.44);
+ margin-right: 5px;
+ }
+ .color-label {
+ padding: 6px 10px;
+ margin-right: 7px;
+ margin-top: 10px;
+ }
+
+ .avatar {
+ float: none;
+ }
+ }
+}
+
+.milestone-detail {
+ border-bottom: 1px solid $border-color;
+ padding: 20px 0;
+}
diff --git a/app/assets/stylesheets/pages/projects.scss b/app/assets/stylesheets/pages/projects.scss
index 13b0ed769f..542ac896f6 100644
--- a/app/assets/stylesheets/pages/projects.scss
+++ b/app/assets/stylesheets/pages/projects.scss
@@ -73,31 +73,26 @@
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: 12px;
+ margin-top: 20px;
margin-bottom: 0px;
.count-buttons {
display: block;
- margin-bottom: 12px;
+ margin-bottom: 20px;
}
.clone-row {
@@ -163,7 +158,7 @@
line-height: 13px;
padding: $gl-vert-padding $gl-padding;
letter-spacing: .4px;
- padding: 10px;
+ padding: 10px 14px;
text-align: center;
vertical-align: middle;
touch-action: manipulation;
@@ -281,36 +276,6 @@
margin-top: -1px;
}
-.top-area {
- border-bottom: 1px solid #EEE;
-
- ul.nav-links {
- display: inline-block;
- width: 50%;
- margin-bottom: 0px;
- border-bottom: none;
- }
-
- .projects-search-form {
- width: 50%;
- display: inline-block;
- float: right;
- padding-top: 11px;
- text-align: right;
-
- .btn-green {
- margin-left: 10px;
- float: right;
- }
- }
-
- @media (max-width: $screen-xs-max) {
- .projects-search-form {
- padding-top: 15px;
- }
- }
-}
-
.fork-namespaces {
.fork-thumbnail {
text-align: center;
@@ -386,22 +351,6 @@ pre.light-well {
border-color: #f1f1f1;
}
-.projects-search-form {
- padding: $gl-padding 0;
- padding-bottom: 0;
- margin-bottom: 0px;
-
- input {
- display: inline-block;
- width: calc(100% - 151px);
- }
-
- .btn {
- display: inline-block;
- width: 135px;
- }
-}
-
.git-empty {
margin: 0 7px 0 7px;
@@ -437,12 +386,11 @@ pre.light-well {
@include basic-list;
.project-row {
- padding: $gl-padding 0;
border-color: $table-border-color;
&.no-description {
.project {
- line-height: 44px;
+ line-height: 40px;
}
}
@@ -455,12 +403,16 @@ pre.light-well {
.project-controls {
float: right;
color: $gl-gray;
- line-height: 45px;
+ line-height: 40px;
color: #7f8fa4;
a:hover {
text-decoration: none;
}
+
+ > span {
+ margin-left: 10px;
+ }
}
.project-description {
@@ -558,3 +510,19 @@ pre.light-well {
width: 101%;
}
}
+
+.cannot-be-merged,
+.cannot-be-merged:hover {
+ color: #E62958;
+ margin-top: 2px;
+}
+
+.private-forks-notice .private-fork-icon {
+ i:nth-child(1) {
+ color: #2AA056;
+ }
+
+ i:nth-child(2) {
+ color: #FFFFFF;
+ }
+}
diff --git a/app/assets/stylesheets/pages/todos.scss b/app/assets/stylesheets/pages/todos.scss
new file mode 100644
index 0000000000..2f57f21963
--- /dev/null
+++ b/app/assets/stylesheets/pages/todos.scss
@@ -0,0 +1,124 @@
+/**
+ * Dashboard Todos
+ *
+ */
+
+.navbar-nav {
+ li {
+ .badge.todos-pending-count {
+ background-color: #7f8fa4;
+ margin-top: -5px;
+ }
+ }
+}
+
+.todos {
+ .panel {
+ border-top: none;
+ margin-bottom: 0;
+ }
+}
+
+.todo-item {
+ font-size: $gl-font-size;
+ padding: $gl-padding-top 0 $gl-padding-top ($gl-avatar-size + $gl-padding-top);
+ border-bottom: 1px solid $table-border-color;
+ color: #7f8fa4;
+
+ &.todo-inline {
+ .avatar {
+ position: relative;
+ top: -2px;
+ }
+
+ .todo-title {
+ line-height: 40px;
+ }
+ }
+
+ 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;
+ }
+ }
+
+ .todo-note-icon {
+ color: #777;
+ float: left;
+ font-size: $gl-font-size;
+ line-height: 16px;
+ margin-right: 5px;
+ }
+ }
+
+ &:last-child { border:none }
+}
+
+@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 c7411617cb..ef63b01060 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/wiki.scss b/app/assets/stylesheets/pages/wiki.scss
index cdf514197c..dfaeba41cf 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 0000000000..26cf74e484
--- /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/application_settings_controller.rb b/app/controllers/admin/application_settings_controller.rb
index 094eef28a4..04a99d8c84 100644
--- a/app/controllers/admin/application_settings_controller.rb
+++ b/app/controllers/admin/application_settings_controller.rb
@@ -74,13 +74,14 @@ class Admin::ApplicationSettingsController < Admin::ApplicationController
:metrics_timeout,
:metrics_method_call_threshold,
:metrics_sample_interval,
- :ip_blocking_enabled,
- :dnsbl_servers_list,
:recaptcha_enabled,
:recaptcha_site_key,
:recaptcha_private_key,
:sentry_enabled,
:sentry_dsn,
+ :akismet_enabled,
+ :akismet_api_key,
+ :email_author_in_body,
restricted_visibility_levels: [],
import_sources: []
)
diff --git a/app/controllers/admin/broadcast_messages_controller.rb b/app/controllers/admin/broadcast_messages_controller.rb
index 4735b27c65..fc34292498 100644
--- a/app/controllers/admin/broadcast_messages_controller.rb
+++ b/app/controllers/admin/broadcast_messages_controller.rb
@@ -2,7 +2,7 @@ class Admin::BroadcastMessagesController < Admin::ApplicationController
before_action :finder, only: [:edit, :update, :destroy]
def index
- @broadcast_messages = BroadcastMessage.reorder("starts_at ASC").page(params[:page])
+ @broadcast_messages = BroadcastMessage.reorder("ends_at DESC").page(params[:page])
@broadcast_message = BroadcastMessage.new
end
@@ -36,6 +36,10 @@ class Admin::BroadcastMessagesController < Admin::ApplicationController
end
end
+ def preview
+ @message = broadcast_message_params[:message]
+ end
+
protected
def finder
diff --git a/app/controllers/admin/labels_controller.rb b/app/controllers/admin/labels_controller.rb
index 3b070e65d0..d79ce2b10f 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/admin/spam_logs_controller.rb b/app/controllers/admin/spam_logs_controller.rb
new file mode 100644
index 0000000000..377e9741e5
--- /dev/null
+++ b/app/controllers/admin/spam_logs_controller.rb
@@ -0,0 +1,17 @@
+class Admin::SpamLogsController < Admin::ApplicationController
+ def index
+ @spam_logs = SpamLog.order(id: :desc).page(params[:page])
+ end
+
+ def destroy
+ spam_log = SpamLog.find(params[:id])
+
+ if params[:remove_user]
+ spam_log.remove_user
+ redirect_to admin_spam_logs_path, notice: "User #{spam_log.user.username} was successfully removed."
+ else
+ spam_log.destroy
+ render nothing: true
+ end
+ end
+end
diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb
index 2d735b9059..1f55b18e0b 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)
@@ -60,6 +60,8 @@ class ApplicationController < ActionController::Base
params[:authenticity_token].presence
elsif params[:private_token].presence
params[:private_token].presence
+ elsif request.headers['PRIVATE-TOKEN'].present?
+ request.headers['PRIVATE-TOKEN']
end
user = user_token && User.find_by_authentication_token(user_token.to_s)
@@ -162,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)
@@ -244,6 +246,8 @@ class ApplicationController < ActionController::Base
def ldap_security_check
if current_user && current_user.requires_ldap_check?
+ return unless current_user.try_obtain_ldap_lease
+
unless Gitlab::LDAP::Access.allowed?(current_user)
sign_out current_user
flash[:alert] = "Access denied for your LDAP account."
@@ -275,9 +279,10 @@ class ApplicationController < ActionController::Base
}
end
- def view_to_html_string(partial)
+ def view_to_html_string(partial, locals = {})
render_to_string(
partial,
+ locals: locals,
layout: false,
formats: [:html]
)
@@ -298,7 +303,8 @@ class ApplicationController < ActionController::Base
end
def set_filters_params
- params[:sort] ||= 'id_desc'
+ set_default_sort
+
params[:scope] = 'all' if params[:scope].blank?
params[:state] = 'opened' if params[:state].blank?
@@ -405,4 +411,31 @@ 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
+ key = if is_a_listing_page_for?('issues') || is_a_listing_page_for?('merge_requests')
+ 'issuable_sort'
+ end
+
+ cookies[key] = params[:sort] if key && params[:sort].present?
+ params[:sort] = cookies[key] if key
+ params[:sort] ||= 'id_desc'
+ end
+
+ def is_a_listing_page_for?(page_type)
+ controller_name, action_name = params.values_at(:controller, :action)
+
+ (controller_name == "projects/#{page_type}" && action_name == 'index') ||
+ (controller_name == 'groups' && action_name == page_type) ||
+ (controller_name == 'dashboard' && action_name == page_type)
+ end
end
diff --git a/app/controllers/ci/application_controller.rb b/app/controllers/ci/application_controller.rb
index c420b59c3a..5bb7d499cd 100644
--- a/app/controllers/ci/application_controller.rb
+++ b/app/controllers/ci/application_controller.rb
@@ -3,52 +3,5 @@ module Ci
def self.railtie_helpers_paths
"app/helpers/ci"
end
-
- private
-
- def authorize_access_project!
- unless can?(current_user, :read_project, project)
- return page_404
- end
- end
-
- def authorize_manage_builds!
- unless can?(current_user, :manage_builds, project)
- return page_404
- end
- end
-
- def authenticate_admin!
- return render_404 unless current_user.is_admin?
- end
-
- def authorize_manage_project!
- unless can?(current_user, :admin_project, project)
- return page_404
- end
- end
-
- def page_404
- render file: "#{Rails.root}/public/404.html", status: 404, layout: false
- end
-
- def default_headers
- headers['X-Frame-Options'] = 'DENY'
- headers['X-XSS-Protection'] = '1; mode=block'
- end
-
- # JSON for infinite scroll via Pager object
- def pager_json(partial, count)
- html = render_to_string(
- partial,
- layout: false,
- formats: [:html]
- )
-
- render json: {
- html: html,
- count: count
- }
- end
end
end
diff --git a/app/controllers/ci/projects_controller.rb b/app/controllers/ci/projects_controller.rb
index 3004c2d27f..081e01a75e 100644
--- a/app/controllers/ci/projects_controller.rb
+++ b/app/controllers/ci/projects_controller.rb
@@ -1,9 +1,9 @@
module Ci
class ProjectsController < Ci::ApplicationController
- before_action :project, except: [:index]
- before_action :authenticate_user!, except: [:index, :build, :badge]
- before_action :authorize_access_project!, except: [:index, :badge]
+ 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
@@ -13,9 +13,14 @@ module Ci
# Project status badge
# Image with build status for sha or ref
+ #
+ # This action in DEPRECATED, this is here only for backwards compatibility
+ # with projects migrated from GitLab CI.
+ #
def badge
- image = Ci::ImageForBuildService.new.execute(@project, params)
+ 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 b9eb0a22f8..787416c17a 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 effd472194..5b09862855 100644
--- a/app/controllers/concerns/issues_action.rb
+++ b/app/controllers/concerns/issues_action.rb
@@ -6,6 +6,8 @@ module IssuesAction
@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 f7a25111db..f6de696e84 100644
--- a/app/controllers/concerns/merge_requests_action.rb
+++ b/app/controllers/concerns/merge_requests_action.rb
@@ -5,5 +5,7 @@ module MergeRequestsAction
@merge_requests = get_merge_requests_collection
@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 58e9049f15..59f29473e9 100644
--- a/app/controllers/dashboard/projects_controller.rb
+++ b/app/controllers/dashboard/projects_controller.rb
@@ -3,7 +3,16 @@ class Dashboard::ProjectsController < Dashboard::ApplicationController
def index
@projects = current_user.authorized_projects.sorted_by_activity.non_archived
+ @projects = @projects.sort(@sort = params[:sort])
@projects = @projects.includes(:namespace)
+
+ terms = params[:filter_projects]
+
+ if terms.present?
+ @projects = @projects.search(terms)
+ end
+
+ @projects = @projects.page(params[:page]).per(PER_PAGE)
@last_push = current_user.recent_push
respond_to do |format|
@@ -13,6 +22,11 @@ class Dashboard::ProjectsController < Dashboard::ApplicationController
load_events
render layout: false
end
+ format.json do
+ render json: {
+ html: view_to_html_string("dashboard/projects/_projects", locals: { projects: @projects })
+ }
+ end
end
end
@@ -20,6 +34,14 @@ class Dashboard::ProjectsController < Dashboard::ApplicationController
@projects = current_user.starred_projects
@projects = @projects.includes(:namespace, :forked_from_project, :tags)
@projects = @projects.sort(@sort = params[:sort])
+
+ terms = params[:filter_projects]
+
+ if terms.present?
+ @projects = @projects.search(terms)
+ end
+
+ @projects = @projects.page(params[:page]).per(PER_PAGE)
@last_push = current_user.recent_push
@groups = []
@@ -27,8 +49,9 @@ class Dashboard::ProjectsController < Dashboard::ApplicationController
format.html
format.json do
- load_events
- pager_json("events/_events", @events.count)
+ render json: {
+ html: view_to_html_string("dashboard/projects/_projects", locals: { projects: @projects })
+ }
end
end
end
@@ -36,7 +59,7 @@ class Dashboard::ProjectsController < Dashboard::ApplicationController
private
def load_events
- @events = Event.in_projects(@projects.pluck(:id))
+ @events = Event.in_projects(@projects)
@events = @event_filter.apply_filter(@events).with_associations
@events = @events.limit(20).offset(params[:offset] || 0)
end
diff --git a/app/controllers/dashboard/todos_controller.rb b/app/controllers/dashboard/todos_controller.rb
new file mode 100644
index 0000000000..43cf8fa71a
--- /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/dashboard_controller.rb b/app/controllers/dashboard_controller.rb
index 087da93508..139e40db18 100644
--- a/app/controllers/dashboard_controller.rb
+++ b/app/controllers/dashboard_controller.rb
@@ -23,14 +23,14 @@ class DashboardController < Dashboard::ApplicationController
protected
def load_events
- project_ids =
+ projects =
if params[:filter] == "starred"
current_user.starred_projects
else
current_user.authorized_projects
- end.pluck(:id)
+ end
- @events = Event.in_projects(project_ids)
+ @events = Event.in_projects(projects)
@events = @event_filter.apply_filter(@events).with_associations
@events = @events.limit(20).offset(params[:offset] || 0)
end
diff --git a/app/controllers/explore/projects_controller.rb b/app/controllers/explore/projects_controller.rb
index a5aeaed66c..317ad83500 100644
--- a/app/controllers/explore/projects_controller.rb
+++ b/app/controllers/explore/projects_controller.rb
@@ -6,19 +6,49 @@ class Explore::ProjectsController < Explore::ApplicationController
@projects = @projects.where(visibility_level: params[:visibility_level]) if params[:visibility_level].present?
@projects = @projects.non_archived
@projects = @projects.search(params[:search]) if params[:search].present?
+ @projects = @projects.search(params[:filter_projects]) if params[:filter_projects].present?
@projects = @projects.sort(@sort = params[:sort])
@projects = @projects.includes(:namespace).page(params[:page]).per(PER_PAGE)
+
+ respond_to do |format|
+ format.html
+ format.json do
+ render json: {
+ html: view_to_html_string("dashboard/projects/_projects", locals: { projects: @projects })
+ }
+ end
+ end
end
def trending
- @trending_projects = TrendingProjectsFinder.new.execute(current_user)
- @trending_projects = @trending_projects.non_archived
- @trending_projects = @trending_projects.page(params[:page]).per(PER_PAGE)
+ @projects = TrendingProjectsFinder.new.execute(current_user)
+ @projects = @projects.non_archived
+ @projects = @projects.search(params[:filter_projects]) if params[:filter_projects].present?
+ @projects = @projects.page(params[:page]).per(PER_PAGE)
+
+ respond_to do |format|
+ format.html
+ format.json do
+ render json: {
+ html: view_to_html_string("dashboard/projects/_projects", locals: { projects: @projects })
+ }
+ end
+ end
end
def starred
- @starred_projects = ProjectsFinder.new.execute(current_user)
- @starred_projects = @starred_projects.reorder('star_count DESC')
- @starred_projects = @starred_projects.page(params[:page]).per(PER_PAGE)
+ @projects = ProjectsFinder.new.execute(current_user)
+ @projects = @projects.search(params[:filter_projects]) if params[:filter_projects].present?
+ @projects = @projects.reorder('star_count DESC')
+ @projects = @projects.page(params[:page]).per(PER_PAGE)
+
+ respond_to do |format|
+ format.html
+ format.json do
+ render json: {
+ html: view_to_html_string("dashboard/projects/_projects", locals: { projects: @projects })
+ }
+ end
+ end
end
end
diff --git a/app/controllers/groups_controller.rb b/app/controllers/groups_controller.rb
index fb26a4e6fc..ca5ce1e204 100644
--- a/app/controllers/groups_controller.rb
+++ b/app/controllers/groups_controller.rb
@@ -2,18 +2,19 @@ class GroupsController < Groups::ApplicationController
include IssuesAction
include MergeRequestsAction
- skip_before_action :authenticate_user!, only: [:show, :issues, :merge_requests]
respond_to :html
- before_action :group, except: [:new, :create]
+
+ skip_before_action :authenticate_user!, only: [:index, :show, :issues, :merge_requests]
+ before_action :group, except: [:index, :new, :create]
# Authorize
- before_action :authorize_read_group!, except: [:show, :new, :create, :autocomplete]
+ before_action :authorize_read_group!, except: [:index, :show, :new, :create, :autocomplete]
before_action :authorize_admin_group!, only: [:edit, :update, :destroy, :projects]
before_action :authorize_create_group!, only: [:new, :create]
# Load group projects
- before_action :load_projects, except: [:new, :create, :projects, :edit, :update, :autocomplete]
- before_action :event_filter, only: :show
+ before_action :load_projects, except: [:index, :new, :create, :projects, :edit, :update, :autocomplete]
+ before_action :event_filter, only: [:show, :events]
layout :determine_layout
@@ -40,13 +41,16 @@ class GroupsController < Groups::ApplicationController
def show
@last_push = current_user.recent_push if current_user
@projects = @projects.includes(:namespace)
+ @projects = @projects.search(params[:filter_projects]) if params[:filter_projects].present?
+ @projects = @projects.page(params[:page]).per(PER_PAGE) if params[:filter_projects].blank?
respond_to do |format|
format.html
format.json do
- load_events
- pager_json("events/_events", @events.count)
+ render json: {
+ html: view_to_html_string("dashboard/projects/_projects", locals: { projects: @projects })
+ }
end
format.atom do
@@ -56,6 +60,15 @@ class GroupsController < Groups::ApplicationController
end
end
+ def events
+ respond_to do |format|
+ format.json do
+ load_events
+ pager_json("events/_events", @events.count)
+ end
+ end
+ end
+
def edit
end
@@ -81,16 +94,13 @@ class GroupsController < Groups::ApplicationController
def group
@group ||= Group.find_by(path: params[:id])
+ @group || render_404
end
def load_projects
@projects ||= ProjectsFinder.new.execute(current_user, group: group).sorted_by_activity.non_archived
end
- def project_ids
- @projects.pluck(:id)
- end
-
# Dont allow unauthorized access to group
def authorize_read_group!
unless @group and (@projects.present? or can?(current_user, :read_group, @group))
@@ -123,7 +133,7 @@ class GroupsController < Groups::ApplicationController
end
def load_events
- @events = Event.in_projects(project_ids)
+ @events = Event.in_projects(@projects)
@events = event_filter.apply_filter(@events).with_associations
@events = @events.limit(20).offset(params[:offset] || 0)
end
diff --git a/app/controllers/omniauth_callbacks_controller.rb b/app/controllers/omniauth_callbacks_controller.rb
index 4c13228fce..21135f7d60 100644
--- a/app/controllers/omniauth_callbacks_controller.rb
+++ b/app/controllers/omniauth_callbacks_controller.rb
@@ -1,4 +1,5 @@
class OmniauthCallbacksController < Devise::OmniauthCallbacksController
+ include AuthenticatesWithTwoFactor
protect_from_forgery except: [:kerberos, :saml, :cas3]
@@ -29,14 +30,38 @@ class OmniauthCallbacksController < Devise::OmniauthCallbacksController
# Do additional LDAP checks for the user filter and EE features
if ldap_user.allowed?
- log_audit_event(@user, with: :ldap)
- sign_in_and_redirect(@user)
+ if @user.two_factor_enabled?
+ prompt_for_two_factor(@user)
+ else
+ log_audit_event(@user, with: :ldap)
+ sign_in_and_redirect(@user)
+ end
else
flash[:alert] = "Access denied for your LDAP account."
redirect_to new_user_session_path
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]
@@ -60,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'])
@@ -99,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/profiles/two_factor_auths_controller.rb b/app/controllers/profiles/two_factor_auths_controller.rb
index 6e91d9b4ad..8f83fdd02b 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 configure Two-Factor Authentication in your account.'
- else
- grace_period_deadline = current_user.otp_grace_period_started_at + two_factor_grace_period.hours
- flash.now[:alert] = "You must configure Two-Factor Authentication in your account until #{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/projects/application_controller.rb b/app/controllers/projects/application_controller.rb
index dd32d50919..a326bc5821 100644
--- a/app/controllers/projects/application_controller.rb
+++ b/app/controllers/projects/application_controller.rb
@@ -28,6 +28,11 @@ class Projects::ApplicationController < ApplicationController
private
+ def apply_diff_view_cookie!
+ view = params[:view] || cookies[:diff_view]
+ cookies.permanent[:diff_view] = params[:view] = view if view
+ end
+
def builds_enabled
return render_404 unless @project.builds_enabled?
end
diff --git a/app/controllers/projects/artifacts_controller.rb b/app/controllers/projects/artifacts_controller.rb
index f159a6d6dc..cfea126651 100644
--- a/app/controllers/projects/artifacts_controller.rb
+++ b/app/controllers/projects/artifacts_controller.rb
@@ -1,6 +1,6 @@
class Projects::ArtifactsController < Projects::ApplicationController
layout 'project'
- before_action :authorize_read_build_artifacts!
+ before_action :authorize_read_build!
def download
unless artifacts_file.file_storage?
@@ -43,14 +43,4 @@ class Projects::ArtifactsController < Projects::ApplicationController
def artifacts_file
@artifacts_file ||= build.artifacts_file
end
-
- def authorize_read_build_artifacts!
- unless can?(current_user, :read_build_artifacts, @project)
- if current_user.nil?
- return authenticate_user!
- else
- return render_404
- end
- end
- end
end
diff --git a/app/controllers/projects/avatars_controller.rb b/app/controllers/projects/avatars_controller.rb
index 548f1b9ebf..f7e6bb3444 100644
--- a/app/controllers/projects/avatars_controller.rb
+++ b/app/controllers/projects/avatars_controller.rb
@@ -2,15 +2,13 @@ class Projects::AvatarsController < Projects::ApplicationController
before_action :project
def show
- @blob = @project.repository.blob_at_branch('master', @project.avatar_in_git)
+ @blob = @repository.blob_at_branch('master', @project.avatar_in_git)
if @blob
headers['X-Content-Type-Options'] = 'nosniff'
- send_data(
- @blob.data,
- type: @blob.mime_type,
- disposition: 'inline',
- filename: @blob.name
- )
+ headers.store(*Gitlab::Workhorse.send_git_blob(@repository, @blob))
+ headers['Content-Disposition'] = 'inline'
+ headers['Content-Type'] = @blob.content_type
+ head :ok # 'render nothing: true' messes up the Content-Type
else
render_404
end
diff --git a/app/controllers/projects/badges_controller.rb b/app/controllers/projects/badges_controller.rb
new file mode 100644
index 0000000000..dc9c96df00
--- /dev/null
+++ b/app/controllers/projects/badges_controller.rb
@@ -0,0 +1,24 @@
+class Projects::BadgesController < Projects::ApplicationController
+ before_action :set_no_cache
+
+ def build
+ respond_to do |format|
+ format.html { render_404 }
+ format.svg do
+ image = Ci::ImageForBuildService.new.execute(project, ref: params[:ref])
+ send_file(image.path, filename: image.name, disposition: 'inline', type: 'image/svg+xml')
+ 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 bb72232edd..495a432347 100644
--- a/app/controllers/projects/blob_controller.rb
+++ b/app/controllers/projects/blob_controller.rb
@@ -33,6 +33,7 @@ class Projects::BlobController < Projects::ApplicationController
def edit
@last_commit = Gitlab::Git::Commit.last_for_path(@repository, @ref, @path).sha
+ blob.load_all_data!(@repository)
end
def update
@@ -51,6 +52,7 @@ class Projects::BlobController < Projects::ApplicationController
def preview
@content = params[:content]
+ @blob.load_all_data!(@repository)
diffy = Diffy::Diff.new(@blob.data, @content, diff: '-U 3', include_diff_info: true)
diff_lines = diffy.diff.scan(/.*\n/)[2..-1]
diff_lines = Gitlab::Diff::Parser.new.parse(diff_lines)
diff --git a/app/controllers/projects/builds_controller.rb b/app/controllers/projects/builds_controller.rb
index 92d9699fe8..f159e169f6 100644
--- a/app/controllers/projects/builds_controller.rb
+++ b/app/controllers/projects/builds_controller.rb
@@ -1,9 +1,8 @@
class Projects::BuildsController < Projects::ApplicationController
before_action :build, except: [:index, :cancel_all]
-
- before_action :authorize_manage_builds!, except: [:index, :show, :status]
-
- layout "project"
+ before_action :authorize_read_build!, except: [:cancel, :cancel_all, :retry]
+ before_action :authorize_update_build!, except: [:index, :show, :status]
+ layout 'project'
def index
@scope = params[:scope]
@@ -23,7 +22,6 @@ class Projects::BuildsController < Projects::ApplicationController
def cancel_all
@project.builds.running_or_pending.each(&:cancel)
-
redirect_to namespace_project_builds_path(project.namespace, project)
end
@@ -46,18 +44,22 @@ class Projects::BuildsController < Projects::ApplicationController
end
build = Ci::Build.retry(@build)
-
redirect_to build_path(build)
end
+ def cancel
+ @build.cancel
+ redirect_to build_path(@build)
+ end
+
def status
render json: @build.to_json(only: [:status, :id, :sha, :coverage], methods: :sha)
end
- def cancel
- @build.cancel
-
- redirect_to build_path(@build)
+ 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
@@ -69,10 +71,4 @@ class Projects::BuildsController < Projects::ApplicationController
def build_path(build)
namespace_project_build_path(build.project.namespace, build.project, build)
end
-
- def authorize_manage_builds!
- unless can?(current_user, :manage_builds, project)
- return render_404
- end
- end
end
diff --git a/app/controllers/projects/commit_controller.rb b/app/controllers/projects/commit_controller.rb
index f5a169e5aa..97d31a4229 100644
--- a/app/controllers/projects/commit_controller.rb
+++ b/app/controllers/projects/commit_controller.rb
@@ -2,16 +2,19 @@
#
# Not to be confused with CommitsController, plural.
class Projects::CommitController < Projects::ApplicationController
+ include CreatesCommit
+
# Authorize
before_action :require_non_empty_project
- before_action :authorize_download_code!, except: [:cancel_builds]
- before_action :authorize_manage_builds!, only: [:cancel_builds]
+ before_action :authorize_download_code!, except: [:cancel_builds, :retry_builds]
+ before_action :authorize_update_build!, only: [:cancel_builds, :retry_builds]
+ before_action :authorize_read_commit_status!, only: [:builds]
before_action :commit
- before_action :authorize_manage_builds!, only: [:cancel_builds, :retry_builds]
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
@note = @project.build_commit_note(commit)
@@ -55,8 +58,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
@@ -66,6 +98,8 @@ class Projects::CommitController < Projects::ApplicationController
end
def define_show_vars
+ return git_not_found! unless commit
+
if params[:w].to_i == 1
@diffs = commit.diffs({ ignore_whitespace_change: true })
else
@@ -78,9 +112,15 @@ class Projects::CommitController < Projects::ApplicationController
@statuses = ci_commit.statuses if ci_commit
end
- def authorize_manage_builds!
- unless can?(current_user, :manage_builds, project)
- return render_404
- 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/commits_controller.rb b/app/controllers/projects/commits_controller.rb
index bf5b54c8cb..1420b96840 100644
--- a/app/controllers/projects/commits_controller.rb
+++ b/app/controllers/projects/commits_controller.rb
@@ -21,6 +21,9 @@ class Projects::CommitsController < Projects::ApplicationController
@note_counts = project.notes.where(commit_id: @commits.map(&:id)).
group(:commit_id).count
+ @merge_request = @project.merge_requests.opened.
+ find_by(source_project: @project, source_branch: @ref, target_branch: @repository.root_ref)
+
respond_to do |format|
format.html
format.json { pager_json("projects/commits/_commits", @commits.size) }
diff --git a/app/controllers/projects/compare_controller.rb b/app/controllers/projects/compare_controller.rb
index 7bbe75b397..dc5d217f3e 100644
--- a/app/controllers/projects/compare_controller.rb
+++ b/app/controllers/projects/compare_controller.rb
@@ -4,24 +4,23 @@ class Projects::CompareController < Projects::ApplicationController
# Authorize
before_action :require_non_empty_project
before_action :authorize_download_code!
+ before_action :assign_ref_vars, only: [:index, :show]
+ before_action :merge_request, only: [:index, :show]
def index
- @ref = Addressable::URI.unescape(params[:to])
end
def show
- base_ref = Addressable::URI.unescape(params[:from])
- @ref = head_ref = Addressable::URI.unescape(params[:to])
diff_options = { ignore_whitespace_change: true } if params[:w] == '1'
compare_result = CompareService.new.
- execute(@project, head_ref, @project, base_ref, diff_options)
+ execute(@project, @head_ref, @project, @base_ref, diff_options)
if compare_result
@commits = Commit.decorate(compare_result.commits, @project)
@diffs = compare_result.diffs
- @commit = @project.commit(head_ref)
- @base_commit = @project.merge_base_commit(base_ref, head_ref)
+ @commit = @project.commit(@head_ref)
+ @base_commit = @project.merge_base_commit(@base_ref, @head_ref)
@diff_refs = [@base_commit, @commit]
@line_notes = []
end
@@ -31,4 +30,16 @@ class Projects::CompareController < Projects::ApplicationController
redirect_to namespace_project_compare_path(@project.namespace, @project,
params[:from], params[:to])
end
+
+ private
+
+ def assign_ref_vars
+ @base_ref = Addressable::URI.unescape(params[:from])
+ @ref = @head_ref = Addressable::URI.unescape(params[:to])
+ end
+
+ def merge_request
+ @merge_request ||= @project.merge_requests.opened.
+ find_by(source_project: @project, source_branch: @head_ref, target_branch: @base_ref)
+ end
end
diff --git a/app/controllers/projects/forks_controller.rb b/app/controllers/projects/forks_controller.rb
index 750181f0c1..a0835c9aad 100644
--- a/app/controllers/projects/forks_controller.rb
+++ b/app/controllers/projects/forks_controller.rb
@@ -3,6 +3,25 @@ class Projects::ForksController < Projects::ApplicationController
before_action :require_non_empty_project
before_action :authorize_download_code!
+ def index
+ base_query = project.forks.includes(:creator)
+
+ @forks = if current_user
+ base_query.where('projects.visibility_level IN (?) OR projects.id IN (?)',
+ Project.public_and_internal_levels,
+ current_user.authorized_projects.pluck(:id))
+ else
+ base_query.where('projects.visibility_level = ?', Project::PUBLIC)
+ end
+
+ @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.order_by(@sort).page(params[:page]).per(PER_PAGE)
+ end
+
def new
@namespaces = current_user.manageable_namespaces
@namespaces.delete(@project.namespace)
@@ -10,7 +29,7 @@ class Projects::ForksController < Projects::ApplicationController
def create
namespace = Namespace.find(params[:namespace_key])
-
+
@forked_project = namespace.projects.find_by(path: project.path)
@forked_project = nil unless @forked_project && @forked_project.forked_from_project == project
@@ -23,7 +42,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 07f355c35b..196996f175 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 6824488380..67faa1e443 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 86d6e3e0f6..ecac3c395e 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 ed3050d59a..5fe2169460 100644
--- a/app/controllers/projects/merge_requests_controller.rb
+++ b/app/controllers/projects/merge_requests_controller.rb
@@ -34,6 +34,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
@@ -57,6 +59,8 @@ class Projects::MergeRequestsController < Projects::ApplicationController
end
def diffs
+ apply_diff_view_cookie!
+
@commit = @merge_request.last_commit
@base_commit = @merge_request.diff_base_commit
@@ -177,6 +181,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 15506bd677..21f30f278c 100644
--- a/app/controllers/projects/milestones_controller.rb
+++ b/app/controllers/projects/milestones_controller.rb
@@ -11,11 +11,12 @@ class Projects::MilestonesController < Projects::ApplicationController
respond_to :html
def index
- @milestones = case params[:state]
- when 'all'; @project.milestones.order("state, due_date DESC")
- when 'closed'; @project.milestones.closed.order("due_date DESC")
- else @project.milestones.active.order("due_date ASC")
- end
+ @milestones =
+ case params[:state]
+ when 'all' then @project.milestones.reorder(due_date: :desc, title: :asc)
+ when 'closed' then @project.milestones.closed.reorder(due_date: :desc, title: :asc)
+ else @project.milestones.active.reorder(due_date: :asc, title: :asc)
+ end
@milestones = @milestones.includes(:project)
@milestones = @milestones.page(params[:page]).per(PER_PAGE)
@@ -34,6 +35,7 @@ class Projects::MilestonesController < Projects::ApplicationController
@issues = @milestone.issues
@users = @milestone.participants.uniq
@merge_requests = @milestone.merge_requests
+ @labels = @milestone.labels
end
def create
diff --git a/app/controllers/projects/notes_controller.rb b/app/controllers/projects/notes_controller.rb
index 6f1e186d40..1b9dd56804 100644
--- a/app/controllers/projects/notes_controller.rb
+++ b/app/controllers/projects/notes_controller.rb
@@ -11,11 +11,9 @@ class Projects::NotesController < Projects::ApplicationController
notes_json = { notes: [], last_fetched_at: current_fetched_at }
@notes.each do |note|
- notes_json[:notes] << {
- id: note.id,
- html: note_to_html(note),
- valid: note.valid?
- }
+ next if note.cross_reference_not_visible_for?(current_user)
+
+ notes_json[:notes] << note_json(note)
end
render json: notes_json
@@ -25,7 +23,7 @@ class Projects::NotesController < Projects::ApplicationController
@note = Notes::CreateService.new(project, current_user, note_params).execute
respond_to do |format|
- format.json { render_note_json(@note) }
+ format.json { render json: note_json(@note) }
format.html { redirect_back_or_default }
end
end
@@ -34,7 +32,7 @@ class Projects::NotesController < Projects::ApplicationController
@note = Notes::UpdateService.new(project, current_user, note_params).execute(note)
respond_to do |format|
- format.json { render_note_json(@note) }
+ format.json { render json: note_json(@note) }
format.html { redirect_back_or_default }
end
end
@@ -99,6 +97,8 @@ class Projects::NotesController < Projects::ApplicationController
end
def note_to_discussion_html(note)
+ return unless note.for_diff_line?
+
if params[:view] == 'parallel'
template = "projects/notes/_diff_notes_with_reply_parallel"
locals =
@@ -106,7 +106,7 @@ class Projects::NotesController < Projects::ApplicationController
{ notes_left: [note], notes_right: [] }
else
{ notes_left: [], notes_right: [note] }
- end
+ end
else
template = "projects/notes/_diff_notes_with_reply"
locals = { notes: [note] }
@@ -131,9 +131,9 @@ class Projects::NotesController < Projects::ApplicationController
)
end
- def render_note_json(note)
+ def note_json(note)
if note.valid?
- render json: {
+ {
valid: true,
id: note.id,
discussion_id: note.discussion_id,
@@ -144,7 +144,7 @@ class Projects::NotesController < Projects::ApplicationController
discussion_with_diff_html: note_to_discussion_with_diff_html(note)
}
else
- render json: {
+ {
valid: false,
award: note.is_award,
errors: note.errors
@@ -163,8 +163,6 @@ class Projects::NotesController < Projects::ApplicationController
)
end
- private
-
def find_current_user_notes
@notes = NotesFinder.new.execute(project, current_user, params)
end
diff --git a/app/controllers/projects/raw_controller.rb b/app/controllers/projects/raw_controller.rb
index be7d5c187f..87b4d08da0 100644
--- a/app/controllers/projects/raw_controller.rb
+++ b/app/controllers/projects/raw_controller.rb
@@ -15,7 +15,10 @@ class Projects::RawController < Projects::ApplicationController
if @blob.lfs_pointer?
send_lfs_object
else
- stream_data
+ headers.store(*Gitlab::Workhorse.send_git_blob(@repository, @blob))
+ headers['Content-Disposition'] = 'inline'
+ headers['Content-Type'] = get_blob_type
+ head :ok # 'render nothing: true' messes up the Content-Type
end
else
render_404
@@ -34,16 +37,6 @@ class Projects::RawController < Projects::ApplicationController
end
end
- def stream_data
- type = get_blob_type
-
- send_data(
- @blob.data,
- type: type,
- disposition: 'inline'
- )
- end
-
def send_lfs_object
lfs_object = find_lfs_object
diff --git a/app/controllers/projects/runner_projects_controller.rb b/app/controllers/projects/runner_projects_controller.rb
index e2785caa2f..bedeb4a295 100644
--- a/app/controllers/projects/runner_projects_controller.rb
+++ b/app/controllers/projects/runner_projects_controller.rb
@@ -1,5 +1,5 @@
class Projects::RunnerProjectsController < Projects::ApplicationController
- before_action :authorize_admin_project!
+ before_action :authorize_admin_build!
layout 'project_settings'
diff --git a/app/controllers/projects/runners_controller.rb b/app/controllers/projects/runners_controller.rb
index 4993b2648a..0dd2d6a99b 100644
--- a/app/controllers/projects/runners_controller.rb
+++ b/app/controllers/projects/runners_controller.rb
@@ -1,6 +1,6 @@
class Projects::RunnersController < Projects::ApplicationController
+ before_action :authorize_admin_build!
before_action :set_runner, only: [:edit, :update, :destroy, :pause, :resume, :show]
- before_action :authorize_admin_project!
layout 'project_settings'
diff --git a/app/controllers/projects/tags_controller.rb b/app/controllers/projects/tags_controller.rb
index 280fe12cc7..e580487a2c 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/triggers_controller.rb b/app/controllers/projects/triggers_controller.rb
index 30adfad1da..92359745ce 100644
--- a/app/controllers/projects/triggers_controller.rb
+++ b/app/controllers/projects/triggers_controller.rb
@@ -1,5 +1,5 @@
class Projects::TriggersController < Projects::ApplicationController
- before_action :authorize_admin_project!
+ before_action :authorize_admin_build!
layout 'project_settings'
diff --git a/app/controllers/projects/variables_controller.rb b/app/controllers/projects/variables_controller.rb
index 10efafea9d..0023465457 100644
--- a/app/controllers/projects/variables_controller.rb
+++ b/app/controllers/projects/variables_controller.rb
@@ -1,5 +1,5 @@
class Projects::VariablesController < Projects::ApplicationController
- before_action :authorize_admin_project!
+ before_action :authorize_admin_build!
layout 'project_settings'
diff --git a/app/controllers/projects_controller.rb b/app/controllers/projects_controller.rb
index 935f7d75c6..aea08ecce3 100644
--- a/app/controllers/projects_controller.rb
+++ b/app/controllers/projects_controller.rb
@@ -93,6 +93,10 @@ class ProjectsController < ApplicationController
return
end
+ if @project.pending_delete?
+ flash[:alert] = "Project queued for delete."
+ end
+
respond_to do |format|
format.html do
if @project.repository_exists?
@@ -120,8 +124,8 @@ class ProjectsController < ApplicationController
def destroy
return access_denied! unless can?(current_user, :remove_project, @project)
- ::Projects::DestroyService.new(@project, current_user, {}).execute
- flash[:alert] = "Project '#{@project.name}' was deleted."
+ ::Projects::DestroyService.new(@project, current_user, {}).pending_delete!
+ flash[:alert] = "Project '#{@project.name}' will be deleted."
redirect_to dashboard_projects_path
rescue Projects::DestroyService::DestroyError => ex
@@ -223,6 +227,7 @@ class ProjectsController < ApplicationController
:issues_enabled, :merge_requests_enabled, :snippets_enabled, :issues_tracker_id, :default_branch,
:wiki_enabled, :visibility_level, :import_url, :last_activity_at, :namespace_id, :avatar,
:builds_enabled, :build_allow_git_fetch, :build_timeout_in_minutes, :build_coverage_regex,
+ :public_builds,
)
end
@@ -231,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/registrations_controller.rb b/app/controllers/registrations_controller.rb
index 5efdd613e7..c48175a4c5 100644
--- a/app/controllers/registrations_controller.rb
+++ b/app/controllers/registrations_controller.rb
@@ -8,11 +8,6 @@ class RegistrationsController < Devise::RegistrationsController
def create
if !Gitlab::Recaptcha.load_configurations! || verify_recaptcha
- if Gitlab::IpCheck.new(request.remote_ip).spam?
- flash[:alert] = 'Could not create an account. This IP is listed for spam.'
- return render action: 'new'
- end
-
super
else
flash[:alert] = "There was an error with the reCAPTCHA code below. Please re-enter the code."
diff --git a/app/controllers/sessions_controller.rb b/app/controllers/sessions_controller.rb
index 825f85199b..44eb58e418 100644
--- a/app/controllers/sessions_controller.rb
+++ b/app/controllers/sessions_controller.rb
@@ -2,6 +2,8 @@ class SessionsController < Devise::SessionsController
include AuthenticatesWithTwoFactor
include Recaptcha::ClientHelper
+ skip_before_action :check_2fa_requirement, only: [:destroy]
+
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]
diff --git a/app/controllers/uploads_controller.rb b/app/controllers/uploads_controller.rb
index 868b05929d..509f4f412c 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 280228dbcc..6055b60608 100644
--- a/app/controllers/users_controller.rb
+++ b/app/controllers/users_controller.rb
@@ -4,8 +4,9 @@ class UsersController < ApplicationController
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
diff --git a/app/finders/issuable_finder.rb b/app/finders/issuable_finder.rb
index 4d56b48e3f..f7240edd61 100644
--- a/app/finders/issuable_finder.rb
+++ b/app/finders/issuable_finder.rb
@@ -81,7 +81,8 @@ class IssuableFinder
elsif current_user && params[:authorized_only].presence && !current_user_related?
@projects = current_user.authorized_projects.reorder(nil)
else
- @projects = ProjectsFinder.new.execute(current_user).reorder(nil)
+ @projects = ProjectsFinder.new.execute(current_user, group: group).
+ reorder(nil)
end
end
@@ -118,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
@@ -252,8 +267,6 @@ class IssuableFinder
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 })
else
- label_names = params[:label_name].split(",")
-
items = items.joins(:labels).where(labels: { title: label_names })
if projects
@@ -265,6 +278,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 0000000000..3ba27c4050
--- /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 c5820bf4c5..e0abc3a286 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 f3a2723ee0..368969c647 100644
--- a/app/helpers/application_helper.rb
+++ b/app/helpers/application_helper.rb
@@ -72,7 +72,7 @@ module ApplicationHelper
if user_or_email.is_a?(User)
user = user_or_email
else
- user = User.find_by(email: user_or_email.downcase)
+ user = User.find_by(email: user_or_email.try(:downcase))
end
if user
@@ -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)
@@ -169,18 +163,6 @@ module ApplicationHelper
Gitlab.config.extra
end
- def search_placeholder
- if @project && @project.persisted?
- 'Search in this project'
- elsif @snippet || @snippets || @show_snippets
- 'Search snippets'
- elsif @group && @group.persisted?
- 'Search in this group'
- else
- 'Search'
- end
- end
-
# Render a `time` element with Javascript-based relative date and tooltip
#
# time - Time object
@@ -224,8 +206,7 @@ module ApplicationHelper
file_content
end
else
- GitHub::Markup.render(file_name, file_content).
- force_encoding(file_content.encoding).html_safe
+ other_markup(file_name, file_content)
end
rescue RuntimeError
simple_format(file_content)
diff --git a/app/helpers/application_settings_helper.rb b/app/helpers/application_settings_helper.rb
index 7d6b58ee21..23693629a4 100644
--- a/app/helpers/application_settings_helper.rb
+++ b/app/helpers/application_settings_helper.rb
@@ -23,6 +23,10 @@ module ApplicationSettingsHelper
current_application_settings.user_oauth_applications
end
+ def askimet_enabled?
+ current_application_settings.akismet_enabled?
+ end
+
# Return a group of checkboxes that use Bootstrap's button plugin for a
# toggle button effect.
def restricted_level_checkboxes(help_block_id)
diff --git a/app/helpers/auth_helper.rb b/app/helpers/auth_helper.rb
index de669e529a..b4f80fd9b3 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 8b689b29a4..1696792792 100644
--- a/app/helpers/blob_helper.rb
+++ b/app/helpers/blob_helper.rb
@@ -36,8 +36,7 @@ module BlobHelper
notice: edit_in_new_fork_notice,
notice_now: edit_in_new_fork_notice_now
}
- fork_path = namespace_project_fork_path(project.namespace, project, namespace_key: current_user.namespace.id,
- continue: continue_params)
+ fork_path = namespace_project_forks_path(project.namespace, project, namespace_key: current_user.namespace.id, continue: continue_params)
link_to "Edit", fork_path, class: 'btn', method: :post
end
@@ -62,8 +61,7 @@ module BlobHelper
notice: edit_in_new_fork_notice + " Try to #{action} this file again.",
notice_now: edit_in_new_fork_notice_now
}
- fork_path = namespace_project_fork_path(project.namespace, project, namespace_key: current_user.namespace.id,
- continue: continue_params)
+ fork_path = namespace_project_forks_path(project.namespace, project, namespace_key: current_user.namespace.id, continue: continue_params)
link_to label, fork_path, class: "btn btn-#{btn_class}", method: :post
end
@@ -128,4 +126,16 @@ module BlobHelper
blob.size
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.
+ def sanitize_svg(blob)
+ blob.data = Loofah.scrub_fragment(blob.data, :strip).to_xml
+ blob
+ end
end
diff --git a/app/helpers/broadcast_messages_helper.rb b/app/helpers/broadcast_messages_helper.rb
index 1ed8c710f7..43a29c96bc 100644
--- a/app/helpers/broadcast_messages_helper.rb
+++ b/app/helpers/broadcast_messages_helper.rb
@@ -3,7 +3,7 @@ module BroadcastMessagesHelper
return unless message.present?
content_tag :div, class: 'broadcast-message', style: broadcast_message_style(message) do
- icon('bullhorn') << ' ' << message.message
+ icon('bullhorn') << ' ' << render_broadcast_message(message.message)
end
end
@@ -31,4 +31,8 @@ module BroadcastMessagesHelper
'Pending'
end
end
+
+ def render_broadcast_message(message)
+ Banzai.render(message, pipeline: :broadcast_message).html_safe
+ end
end
diff --git a/app/helpers/commits_helper.rb b/app/helpers/commits_helper.rb
index d26f007c8e..a09e91578b 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
@@ -152,7 +183,7 @@ module CommitsHelper
options = {
class: "commit-#{options[:source]}-link has_tooltip",
- data: { :'original-title' => sanitize(source_email) }
+ data: { 'original-title'.to_sym => sanitize(source_email) }
}
if user.nil?
diff --git a/app/helpers/diff_helper.rb b/app/helpers/diff_helper.rb
index 62971d1e14..d76db867c5 100644
--- a/app/helpers/diff_helper.rb
+++ b/app/helpers/diff_helper.rb
@@ -1,4 +1,13 @@
module DiffHelper
+ def mark_inline_diffs(old_line, new_line)
+ old_diffs, new_diffs = Gitlab::Diff::InlineDiff.new(old_line, new_line).inline_diffs
+
+ marked_old_line = Gitlab::Diff::InlineDiffMarker.new(old_line).mark(old_diffs)
+ marked_new_line = Gitlab::Diff::InlineDiffMarker.new(new_line).mark(new_diffs)
+
+ [marked_old_line, marked_new_line]
+ end
+
def diff_view
params[:view] == 'parallel' ? 'parallel' : 'inline'
end
@@ -55,12 +64,12 @@ module DiffHelper
if line.blank?
" ".html_safe
else
- line.html_safe
+ line
end
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)
@@ -128,7 +137,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/explore_helper.rb b/app/helpers/explore_helper.rb
index 0d291f9a87..3648757428 100644
--- a/app/helpers/explore_helper.rb
+++ b/app/helpers/explore_helper.rb
@@ -10,8 +10,19 @@ module ExploreHelper
options = exist_opts.merge(options)
- path = explore_projects_path
+ path = if explore_controller?
+ explore_projects_path
+ elsif current_action?(:starred)
+ starred_dashboard_projects_path
+ else
+ dashboard_projects_path
+ end
+
path << "?#{options.to_param}"
path
end
+
+ def explore_controller?
+ controller.class.name.split("::").first == "Explore"
+ end
end
diff --git a/app/helpers/gitlab_markdown_helper.rb b/app/helpers/gitlab_markdown_helper.rb
index 1a22625225..89d2a64849 100644
--- a/app/helpers/gitlab_markdown_helper.rb
+++ b/app/helpers/gitlab_markdown_helper.rb
@@ -78,6 +78,21 @@ module GitlabMarkdownHelper
)
end
+ def other_markup(file_name, text)
+ Gitlab::OtherMarkup.render(
+ file_name,
+ text,
+ project: @project,
+ current_user: (current_user if defined?(current_user)),
+
+ # RelativeLinkFilter
+ project_wiki: @project_wiki,
+ requested_path: @path,
+ ref: @ref,
+ commit: @commit
+ )
+ end
+
# Return the first line of +text+, up to +max_chars+, after parsing the line
# as Markdown. HTML tags in the parsed output are not counted toward the
# +max_chars+ limit. If the length limit falls within a tag's contents, then
diff --git a/app/helpers/icons_helper.rb b/app/helpers/icons_helper.rb
index 5724d3aabe..84c6d0883b 100644
--- a/app/helpers/icons_helper.rb
+++ b/app/helpers/icons_helper.rb
@@ -7,7 +7,7 @@ module IconsHelper
# font-awesome-rails gem, but should we ever use a different icon pack in the
# future we won't have to change hundreds of method calls.
def icon(names, options = {})
- fa_icon(names, options)
+ options.include?(:base) ? fa_stacked_icon(names, options) : fa_icon(names, options)
end
def spinner(text = nil, visible = false)
diff --git a/app/helpers/issuables_helper.rb b/app/helpers/issuables_helper.rb
new file mode 100644
index 0000000000..91a3aa371e
--- /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/issues_helper.rb b/app/helpers/issues_helper.rb
index 43262d579e..ae4ebc0854 100644
--- a/app/helpers/issues_helper.rb
+++ b/app/helpers/issues_helper.rb
@@ -44,14 +44,14 @@ module IssuesHelper
end
def bulk_update_milestone_options
- milestones = project_active_milestones.to_a
+ milestones = @project.milestones.active.reorder(due_date: :asc, title: :asc).to_a
milestones.unshift(Milestone::None)
options_from_collection_for_select(milestones, 'id', 'title', params[:milestone_id])
end
def milestone_options(object)
- milestones = object.project.milestones.active.to_a
+ milestones = object.project.milestones.active.reorder(due_date: :asc, title: :asc).to_a
milestones.unshift(Milestone::None)
options_from_collection_for_select(milestones, 'id', 'title', object.milestone_id)
@@ -69,7 +69,7 @@ module IssuesHelper
end
end
- def issue_button_visibility(issue, closed)
+ def issue_button_visibility(issue, closed)
return 'hidden' if issue.closed? == closed
end
diff --git a/app/helpers/labels_helper.rb b/app/helpers/labels_helper.rb
index a2c3d4d2f3..1c7fcc13b4 100644
--- a/app/helpers/labels_helper.rb
+++ b/app/helpers/labels_helper.rb
@@ -7,6 +7,8 @@ module LabelsHelper
# project - Project object which will be used as the context for the label's
# link. If omitted, defaults to `@project`, or the label's own
# project.
+ # type - The type of item the link will point to (:issue or
+ # :merge_request). If omitted, defaults to :issue.
# block - An optional block that will be passed to `link_to`, forming the
# body of the link element. If omitted, defaults to
# `render_colored_label`.
@@ -23,14 +25,19 @@ module LabelsHelper
# # Force the generated link to use a provided project
# link_to_label(label, project: Project.last)
#
+ # # Force the generated link to point to merge requests instead of issues
+ # link_to_label(label, type: :merge_request)
+ #
# # Customize link body with a block
# link_to_label(label) { "My Custom Label Text" }
#
# Returns a String
- def link_to_label(label, project: nil, &block)
+ def link_to_label(label, project: nil, type: :issue, &block)
project ||= @project || label.project
- link = namespace_project_issues_path(project.namespace, project,
- label_name: label.name)
+ link = send("namespace_project_#{type.to_s.pluralize}_path",
+ project.namespace,
+ project,
+ label_name: label.name)
if block_given?
link_to link, &block
@@ -83,7 +90,11 @@ module LabelsHelper
end
def text_color_for_bg(bg_color)
- r, g, b = bg_color.slice(1,7).scan(/.{2}/).map(&:hex)
+ if bg_color.length == 4
+ r, g, b = bg_color[1, 4].scan(/./).map { |v| (v * 2).hex }
+ else
+ r, g, b = bg_color[1, 7].scan(/.{2}/).map(&:hex)
+ end
if (r + g + b) > 500
'#333333'
diff --git a/app/helpers/milestones_helper.rb b/app/helpers/milestones_helper.rb
index a42cbcff18..7de81d8dfd 100644
--- a/app/helpers/milestones_helper.rb
+++ b/app/helpers/milestones_helper.rb
@@ -36,4 +36,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 e6fb8670e5..5d86bd490a 100644
--- a/app/helpers/nav_helper.rb
+++ b/app/helpers/nav_helper.rb
@@ -19,6 +19,20 @@ module NavHelper
end
end
+ def page_gutter_class
+ 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"
+ else
+ "page-gutter right-sidebar-expanded"
+ end
+ end
+ end
+
def nav_header_class
if nav_menu_collapsed?
"header-collapsed"
diff --git a/app/helpers/projects_helper.rb b/app/helpers/projects_helper.rb
index 77ba612548..d6fb629b0c 100644
--- a/app/helpers/projects_helper.rb
+++ b/app/helpers/projects_helper.rb
@@ -20,6 +20,12 @@ module ProjectsHelper
end
end
+ def link_to_member_avatar(author, opts = {})
+ default_opts = { avatar: true, name: true, size: 16, author_class: 'author', title: ":name" }
+ opts = default_opts.merge(opts)
+ image_tag(avatar_icon(author, opts[:size]), width: opts[:size], class: "avatar avatar-inline #{"s#{opts[:size]}" if opts[:size]}", alt:'') if opts[:avatar]
+ end
+
def link_to_member(project, author, opts = {})
default_opts = { avatar: true, name: true, size: 16, author_class: 'author', title: ":name" }
opts = default_opts.merge(opts)
@@ -40,7 +46,7 @@ module ProjectsHelper
link_to(author_html, user_path(author), class: "author_link").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' => title, container: 'body' } ).html_safe
+ link_to(author_html, user_path(author), class: "author_link has_tooltip", data: { 'original-title'.to_sym => title, container: 'body' } ).html_safe
end
end
@@ -53,14 +59,23 @@ module ProjectsHelper
link_to(simple_sanitize(owner.name), user_path(owner))
end
- project_link = link_to(simple_sanitize(project.name), project_path(project))
+ project_link = link_to project_path(project), { class: "project-item-select-holder" } do
+ link_output = simple_sanitize(project.name)
+
+ if current_user
+ link_output += project_select_tag :project_path,
+ class: "project-item-select js-projects-dropdown",
+ data: { include_groups: false, order_by: 'last_activity_at' }
+ end
+
+ link_output
+ end
+ project_link += icon "chevron-down", class: "dropdown-toggle-caret js-projects-dropdown-toggle" if current_user
full_title = namespace_link + ' / ' + project_link
full_title += ' · '.html_safe + link_to(simple_sanitize(name), url) if name
- content_tag :span do
- full_title
- end
+ full_title
end
def remove_project_message(project)
@@ -83,10 +98,6 @@ module ProjectsHelper
project_nav_tabs.include? name
end
- def project_active_milestones
- @project.milestones.active.order("due_date, title ASC")
- end
-
def project_for_deploy_key(deploy_key)
if deploy_key.projects.include?(@project)
@project
@@ -116,7 +127,7 @@ module ProjectsHelper
private
def get_project_nav_tabs(project, current_user)
- nav_tabs = [:home]
+ nav_tabs = [:home, :forks]
if !project.empty_repo? && can?(current_user, :download_code, project)
nav_tabs << [:files, :commits, :network, :graphs]
@@ -126,7 +137,7 @@ module ProjectsHelper
nav_tabs << :merge_requests
end
- if project.builds_enabled? && can?(current_user, :read_build, project)
+ if can?(current_user, :read_build, project)
nav_tabs << :builds
end
diff --git a/app/helpers/search_helper.rb b/app/helpers/search_helper.rb
index d4f7825862..1eb790b179 100644
--- a/app/helpers/search_helper.rb
+++ b/app/helpers/search_helper.rb
@@ -70,7 +70,7 @@ module SearchHelper
# Autocomplete results for the current user's groups
def groups_autocomplete(term, limit = 5)
- Group.search(term).limit(limit).map do |group|
+ current_user.authorized_groups.search(term).limit(limit).map do |group|
{
label: "group: #{search_result_sanitize(group.name)}",
url: group_path(group)
@@ -80,7 +80,7 @@ module SearchHelper
# Autocomplete results for the current user's projects
def projects_autocomplete(term, limit = 5)
- ProjectsFinder.new.execute(current_user).search_by_title(term).
+ current_user.authorized_projects.search_by_title(term).
sorted_by_stars.non_archived.limit(limit).map do |p|
{
label: "project: #{search_result_sanitize(p.name_with_namespace)}",
diff --git a/app/helpers/snippets_helper.rb b/app/helpers/snippets_helper.rb
index 906cb12cd4..41ae404899 100644
--- a/app/helpers/snippets_helper.rb
+++ b/app/helpers/snippets_helper.rb
@@ -17,4 +17,79 @@ module SnippetsHelper
snippet_path(snippet)
end
end
+
+ # Get an array of line numbers surrounding a matching
+ # line, bounded by min/max.
+ #
+ # @returns Array of line numbers
+ def bounded_line_numbers(line, min, max, surrounding_lines)
+ lower = line - surrounding_lines > min ? line - surrounding_lines : min
+ upper = line + surrounding_lines < max ? line + surrounding_lines : max
+ (lower..upper).to_a
+ end
+
+ # Returns a sorted set of lines to be included in a snippet preview.
+ # This ensures matching adjacent lines do not display duplicated
+ # surrounding code.
+ #
+ # @returns Array, unique and sorted.
+ def matching_lines(lined_content, surrounding_lines, query)
+ used_lines = []
+ lined_content.each_with_index do |line, line_number|
+ used_lines.concat bounded_line_numbers(
+ line_number,
+ 0,
+ lined_content.size,
+ surrounding_lines
+ ) if line.include?(query)
+ end
+
+ used_lines.uniq.sort
+ end
+
+ # 'Chunkify' entire snippet. Splits the snippet data into matching lines +
+ # surrounding_lines() worth of unmatching lines.
+ #
+ # @returns a hash with {snippet_object, snippet_chunks:{data,start_line}}
+ def chunk_snippet(snippet, query, surrounding_lines = 3)
+ lined_content = snippet.content.split("\n")
+ used_lines = matching_lines(lined_content, surrounding_lines, query)
+
+ snippet_chunk = []
+ snippet_chunks = []
+ snippet_start_line = 0
+ last_line = -1
+
+ # Go through each used line, and add consecutive lines as a single chunk
+ # to the snippet chunk array.
+ used_lines.each do |line_number|
+ if last_line < 0
+ # Start a new chunk.
+ snippet_start_line = line_number
+ snippet_chunk << lined_content[line_number]
+ elsif last_line == line_number - 1
+ # Consecutive line, continue chunk.
+ snippet_chunk << lined_content[line_number]
+ else
+ # Non-consecutive line, add chunk to chunk array.
+ snippet_chunks << {
+ data: snippet_chunk.join("\n"),
+ start_line: snippet_start_line + 1
+ }
+
+ # Start a new chunk.
+ snippet_chunk = [lined_content[line_number]]
+ snippet_start_line = line_number
+ end
+ last_line = line_number
+ end
+ # Add final chunk to chunk array
+ snippet_chunks << {
+ data: snippet_chunk.join("\n"),
+ start_line: snippet_start_line + 1
+ }
+
+ # Return snippet with chunk array
+ { snippet_object: snippet, snippet_chunks: snippet_chunks }
+ end
end
diff --git a/app/helpers/sorting_helper.rb b/app/helpers/sorting_helper.rb
index 241179b021..f9026b887d 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 0000000000..4b745a5b96
--- /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 2ad7c80dae..4920ca5af6 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/mailers/email_rejection_mailer.rb b/app/mailers/email_rejection_mailer.rb
index 883f1c73ad..76db31a4c4 100644
--- a/app/mailers/email_rejection_mailer.rb
+++ b/app/mailers/email_rejection_mailer.rb
@@ -10,7 +10,7 @@ class EmailRejectionMailer < BaseMailer
subject: "[Rejected] #{@original_message.subject}"
}
- headers['Message-ID'] = SecureRandom.hex
+ headers['Message-ID'] = "<#{SecureRandom.hex}@#{Gitlab.config.gitlab.host}>"
headers['In-Reply-To'] = @original_message.message_id
headers['References'] = @original_message.message_id
diff --git a/app/mailers/emails/builds.rb b/app/mailers/emails/builds.rb
index 64c1ce8cfa..2f86d1be57 100644
--- a/app/mailers/emails/builds.rb
+++ b/app/mailers/emails/builds.rb
@@ -3,26 +3,27 @@ module Emails
def build_fail_email(build_id, to)
@build = Ci::Build.find(build_id)
@project = @build.project
+
add_project_headers
- add_build_headers
- headers['X-GitLab-Build-Status'] = "failed"
+ add_build_headers('failed')
mail(to: to, subject: subject("Build failed for #{@project.name}", @build.short_sha))
end
def build_success_email(build_id, to)
@build = Ci::Build.find(build_id)
@project = @build.project
+
add_project_headers
- add_build_headers
- headers['X-GitLab-Build-Status'] = "success"
+ add_build_headers('success')
mail(to: to, subject: subject("Build success for #{@project.name}", @build.short_sha))
end
private
- def add_build_headers
+
+ def add_build_headers(status)
headers['X-GitLab-Build-Id'] = @build.id
headers['X-GitLab-Build-Ref'] = @build.ref
+ headers['X-GitLab-Build-Status'] = status.to_s
end
-
end
end
diff --git a/app/models/ability.rb b/app/models/ability.rb
index ab59a3506a..a866eadeeb 100644
--- a/app/models/ability.rb
+++ b/app/models/ability.rb
@@ -5,17 +5,18 @@ class Ability
return [] unless user.is_a?(User)
return [] if user.blocked?
- case subject.class.name
- when "Project" then project_abilities(user, subject)
- when "Issue" then issue_abilities(user, subject)
- when "Note" then note_abilities(user, subject)
- when "ProjectSnippet" then project_snippet_abilities(user, subject)
- when "PersonalSnippet" then personal_snippet_abilities(user, subject)
- when "MergeRequest" then merge_request_abilities(user, subject)
- when "Group" then group_abilities(user, subject)
- when "Namespace" then namespace_abilities(user, subject)
- when "GroupMember" then group_member_abilities(user, subject)
- when "ProjectMember" then project_member_abilities(user, subject)
+ case subject
+ when CommitStatus then commit_status_abilities(user, subject)
+ when Project then project_abilities(user, subject)
+ when Issue then issue_abilities(user, subject)
+ when Note then note_abilities(user, subject)
+ when ProjectSnippet then project_snippet_abilities(user, subject)
+ when PersonalSnippet then personal_snippet_abilities(user, subject)
+ when MergeRequest then merge_request_abilities(user, subject)
+ when Group then group_abilities(user, subject)
+ when Namespace then namespace_abilities(user, subject)
+ when GroupMember then group_member_abilities(user, subject)
+ when ProjectMember then project_member_abilities(user, subject)
else []
end.concat(global_abilities(user))
end
@@ -25,6 +26,8 @@ class Ability
case true
when subject.is_a?(PersonalSnippet)
anonymous_personal_snippet_abilities(subject)
+ when subject.is_a?(CommitStatus)
+ anonymous_commit_status_abilities(subject)
when subject.is_a?(Project) || subject.respond_to?(:project)
anonymous_project_abilities(subject)
when subject.is_a?(Group) || subject.respond_to?(:group)
@@ -52,16 +55,26 @@ class Ability
:read_project_member,
:read_merge_request,
:read_note,
- :read_build,
+ :read_commit_status,
:download_code
]
+ # Allow to read builds by anonymous user if guests are allowed
+ rules << :read_build if project.public_builds?
+
rules - project_disabled_features_rules(project)
else
[]
end
end
+ def anonymous_commit_status_abilities(subject)
+ rules = anonymous_project_abilities(subject.project)
+ # If subject is Ci::Build which inherits from CommitStatus filter the abilities
+ rules = filter_build_abilities(rules) if subject.is_a?(Ci::Build)
+ rules
+ end
+
def anonymous_group_abilities(subject)
group = if subject.is_a?(Group)
subject
@@ -113,6 +126,9 @@ class Ability
if project.public? || project.internal?
rules.push(*public_project_rules)
+
+ # Allow to read builds for internal projects
+ rules << :read_build if project.public_builds?
end
if project.owner == user || user.admin?
@@ -134,7 +150,8 @@ class Ability
def public_project_rules
@public_project_rules ||= project_guest_rules + [
:download_code,
- :fork_project
+ :fork_project,
+ :read_commit_status,
]
end
@@ -149,7 +166,6 @@ class Ability
:read_project_member,
:read_merge_request,
:read_note,
- :read_build,
:create_project,
:create_issue,
:create_note
@@ -158,24 +174,26 @@ class Ability
def project_report_rules
@project_report_rules ||= project_guest_rules + [
- :create_commit_status,
- :read_commit_statuses,
- :read_build_artifacts,
:download_code,
:fork_project,
:create_project_snippet,
:update_issue,
:admin_issue,
- :admin_label
+ :admin_label,
+ :read_commit_status,
+ :read_build,
]
end
def project_dev_rules
@project_dev_rules ||= project_report_rules + [
:admin_merge_request,
+ :create_commit_status,
+ :update_commit_status,
+ :create_build,
+ :update_build,
:create_merge_request,
:create_wiki,
- :manage_builds,
:push_code
]
end
@@ -201,7 +219,9 @@ class Ability
:admin_merge_request,
:admin_note,
:admin_wiki,
- :admin_project
+ :admin_project,
+ :admin_commit_status,
+ :admin_build
]
end
@@ -240,6 +260,10 @@ class Ability
rules += named_abilities('wiki')
end
+ unless project.builds_enabled
+ rules += named_abilities('build')
+ end
+
rules
end
@@ -376,6 +400,22 @@ class Ability
rules
end
+ def commit_status_abilities(user, subject)
+ rules = project_abilities(user, subject.project)
+ # If subject is Ci::Build which inherits from CommitStatus filter the abilities
+ rules = filter_build_abilities(rules) if subject.is_a?(Ci::Build)
+ rules
+ end
+
+ def filter_build_abilities(rules)
+ # If we can't read build we should also not have that
+ # ability when looking at this in context of commit_status
+ %w(read create update admin).each do |rule|
+ rules.delete(:"#{rule}_commit_status") unless rules.include?(:"#{rule}_build")
+ end
+ rules
+ end
+
def abilities
@abilities ||= begin
abilities = Six.new
diff --git a/app/models/abuse_report.rb b/app/models/abuse_report.rb
index 2bc15c60d5..cc59aa4e91 100644
--- a/app/models/abuse_report.rb
+++ b/app/models/abuse_report.rb
@@ -17,7 +17,7 @@ class AbuseReport < ActiveRecord::Base
validates :reporter, presence: true
validates :user, presence: true
validates :message, presence: true
- validates :user_id, uniqueness: true
+ validates :user_id, uniqueness: { message: 'has already been reported' }
def remove_user
user.block
diff --git a/app/models/appearance.rb b/app/models/appearance.rb
new file mode 100644
index 0000000000..4cf8dd9a8c
--- /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/application_setting.rb b/app/models/application_setting.rb
index 2f3487b53a..269056e0e7 100644
--- a/app/models/application_setting.rb
+++ b/app/models/application_setting.rb
@@ -43,8 +43,7 @@
# metrics_port :integer default(8089)
# sentry_enabled :boolean default(FALSE)
# sentry_dsn :string
-# ip_blocking_enabled :boolean default(FALSE)
-# dns_blacklist_threshold :float default(0.33)
+# email_author_in_body :boolean default(FALSE)
#
class ApplicationSetting < ActiveRecord::Base
@@ -72,8 +71,8 @@ class ApplicationSetting < ActiveRecord::Base
url: true
validates :admin_notification_email,
- allow_blank: true,
- email: true
+ email: true,
+ allow_blank: true
validates :two_factor_grace_period,
numericality: { greater_than_or_equal_to: 0 }
@@ -90,6 +89,14 @@ class ApplicationSetting < ActiveRecord::Base
presence: true,
if: :sentry_enabled
+ validates :akismet_api_key,
+ presence: true,
+ if: :akismet_enabled
+
+ validates :max_attachment_size,
+ presence: true,
+ numericality: { only_integer: true, greater_than: 0 }
+
validates_each :restricted_visibility_levels do |record, attr, value|
unless value.nil?
value.each do |level|
@@ -145,7 +152,9 @@ class ApplicationSetting < ActiveRecord::Base
shared_runners_enabled: Settings.gitlab_ci['shared_runners_enabled'],
max_artifacts_size: Settings.artifacts['max_size'],
require_two_factor_authentication: false,
- two_factor_grace_period: 48
+ two_factor_grace_period: 48,
+ recaptcha_enabled: false,
+ akismet_enabled: false
)
end
diff --git a/app/models/broadcast_message.rb b/app/models/broadcast_message.rb
index 6111963371..8a0a8a4c2a 100644
--- a/app/models/broadcast_message.rb
+++ b/app/models/broadcast_message.rb
@@ -26,7 +26,9 @@ class BroadcastMessage < ActiveRecord::Base
default_value_for :font, '#FFFFFF'
def self.current
- where("ends_at > :now AND starts_at <= :now", now: Time.zone.now).last
+ Rails.cache.fetch("broadcast_message_current", expires_in: 1.minute) do
+ where("ends_at > :now AND starts_at <= :now", now: Time.zone.now).last
+ end
end
def active?
diff --git a/app/models/ci/build.rb b/app/models/ci/build.rb
index 623edd8bc5..1227458e52 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/commit.rb b/app/models/ci/commit.rb
index d2a2923694..ecbd2078b1 100644
--- a/app/models/ci/commit.rb
+++ b/app/models/ci/commit.rb
@@ -205,7 +205,11 @@ module Ci
end
def ci_yaml_file
- @ci_yaml_file ||= project.repository.blob_at(sha, '.gitlab-ci.yml').data
+ @ci_yaml_file ||= begin
+ blob = project.repository.blob_at(sha, '.gitlab-ci.yml')
+ blob.load_all_data!(project.repository)
+ blob.data
+ end
rescue
nil
end
diff --git a/app/models/ci/runner.rb b/app/models/ci/runner.rb
index 38b20cd7fa..e725a6d468 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 0ba7b584d9..3224f5457f 100644
--- a/app/models/commit.rb
+++ b/app/models/commit.rb
@@ -68,18 +68,18 @@ class Commit
# Pattern used to extract commit references from text
#
- # The SHA can be between 6 and 40 hex characters.
+ # The SHA can be between 7 and 40 hex characters.
#
# This pattern supports cross-project references.
def self.reference_pattern
%r{
(?:#{Project.reference_pattern}#{reference_prefix})?
- (?\h{6,40})
+ (?\h{7,40})
}x
end
def self.link_reference_pattern
- super("commit", /(?\h{6,40})/)
+ super("commit", /(?\h{7,40})/)
end
def to_reference(from_project = nil)
@@ -215,6 +215,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_range.rb b/app/models/commit_range.rb
index 14e7971fa0..289dbc5728 100644
--- a/app/models/commit_range.rb
+++ b/app/models/commit_range.rb
@@ -32,8 +32,8 @@ class CommitRange
PATTERN = /#{REF_PATTERN}\.{2,3}#{REF_PATTERN}/
# In text references, the beginning and ending refs can only be SHAs
- # between 6 and 40 hex characters.
- STRICT_PATTERN = /\h{6,40}\.{2,3}\h{6,40}/
+ # between 7 and 40 hex characters.
+ STRICT_PATTERN = /\h{7,40}\.{2,3}\h{7,40}/
def self.reference_prefix
'@'
diff --git a/app/models/commit_status.rb b/app/models/commit_status.rb
index 66e0502fc0..7ef5083632 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 04650a9e67..e5f089fb8a 100644
--- a/app/models/concerns/issuable.rb
+++ b/app/models/concerns/issuable.rb
@@ -69,10 +69,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?
@@ -126,17 +151,17 @@ module Issuable
end
def to_hook_data(user)
- {
+ 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
+
+ hook_data
end
def label_names
diff --git a/app/models/email.rb b/app/models/email.rb
index 935705e2ed..b323d1edd1 100644
--- a/app/models/email.rb
+++ b/app/models/email.rb
@@ -15,7 +15,7 @@ class Email < ActiveRecord::Base
belongs_to :user
validates :user_id, presence: true
- validates :email, presence: true, email: { strict_mode: true }, uniqueness: true
+ validates :email, presence: true, uniqueness: true, email: true
validate :unique_email, if: ->(email) { email.email_changed? }
before_validation :cleanup_email
diff --git a/app/models/event.rb b/app/models/event.rb
index 01d008035a..9a0bbf50f8 100644
--- a/app/models/event.rb
+++ b/app/models/event.rb
@@ -47,7 +47,11 @@ class Event < ActiveRecord::Base
# Scopes
scope :recent, -> { reorder(id: :desc) }
scope :code_push, -> { where(action: PUSHED) }
- scope :in_projects, ->(project_ids) { where(project_id: project_ids).recent }
+
+ scope :in_projects, ->(projects) do
+ where(project_id: projects.map(&:id)).recent
+ end
+
scope :with_associations, -> { includes(project: :namespace) }
scope :for_milestone_id, ->(milestone_id) { where(target_type: "Milestone", target_id: milestone_id) }
@@ -64,12 +68,6 @@ class Event < ActiveRecord::Base
[Event::CREATED, Event::CLOSED, Event::MERGED])
end
- def latest_update_time
- row = select(:updated_at, :project_id).reorder(id: :desc).take
-
- row ? row.updated_at : nil
- end
-
def limit_recent(limit = 20, offset = nil)
recent.limit(limit).offset(offset)
end
diff --git a/app/models/external_issue.rb b/app/models/external_issue.rb
index 49f6c95e04..2ca79df0a2 100644
--- a/app/models/external_issue.rb
+++ b/app/models/external_issue.rb
@@ -31,7 +31,7 @@ class ExternalIssue
# Pattern used to extract `JIRA-123` issue references from text
def self.reference_pattern
- %r{(?([A-Z\-]+-)\d+)}
+ %r{(?\b([A-Z][A-Z0-9_]+-)\d+)}
end
def to_reference(_from_project = nil)
diff --git a/app/models/group.rb b/app/models/group.rb
index 5a31b46920..76042b3e3f 100644
--- a/app/models/group.rb
+++ b/app/models/group.rb
@@ -19,7 +19,7 @@ require 'file_size_validator'
class Group < Namespace
include Gitlab::ConfigHelper
include Referable
-
+
has_many :group_members, dependent: :destroy, as: :source, class_name: 'GroupMember'
alias_method :members, :group_members
has_many :users, through: :group_members
diff --git a/app/models/hooks/project_hook.rb b/app/models/hooks/project_hook.rb
index fa18ba5dbb..fe923fafbe 100644
--- a/app/models/hooks/project_hook.rb
+++ b/app/models/hooks/project_hook.rb
@@ -3,11 +3,11 @@
# Table name: web_hooks
#
# id :integer not null, primary key
-# url :string(255)
+# url :string(2000)
# project_id :integer
# created_at :datetime
# updated_at :datetime
-# type :string(255) default("ProjectHook")
+# type :string default("ProjectHook")
# service_id :integer
# push_events :boolean default(TRUE), not null
# issues_events :boolean default(FALSE), not null
diff --git a/app/models/hooks/service_hook.rb b/app/models/hooks/service_hook.rb
index b333a33734..80962264ba 100644
--- a/app/models/hooks/service_hook.rb
+++ b/app/models/hooks/service_hook.rb
@@ -3,11 +3,11 @@
# Table name: web_hooks
#
# id :integer not null, primary key
-# url :string(255)
+# url :string(2000)
# project_id :integer
# created_at :datetime
# updated_at :datetime
-# type :string(255) default("ProjectHook")
+# type :string default("ProjectHook")
# service_id :integer
# push_events :boolean default(TRUE), not null
# issues_events :boolean default(FALSE), not null
diff --git a/app/models/hooks/system_hook.rb b/app/models/hooks/system_hook.rb
index d81512fae5..c147d8762a 100644
--- a/app/models/hooks/system_hook.rb
+++ b/app/models/hooks/system_hook.rb
@@ -3,11 +3,11 @@
# Table name: web_hooks
#
# id :integer not null, primary key
-# url :string(255)
+# url :string(2000)
# project_id :integer
# created_at :datetime
# updated_at :datetime
-# type :string(255) default("ProjectHook")
+# type :string default("ProjectHook")
# service_id :integer
# push_events :boolean default(TRUE), not null
# issues_events :boolean default(FALSE), not null
diff --git a/app/models/hooks/web_hook.rb b/app/models/hooks/web_hook.rb
index 3bb50c63ca..7a13c3f0a3 100644
--- a/app/models/hooks/web_hook.rb
+++ b/app/models/hooks/web_hook.rb
@@ -3,11 +3,11 @@
# Table name: web_hooks
#
# id :integer not null, primary key
-# url :string(255)
+# url :string(2000)
# project_id :integer
# created_at :datetime
# updated_at :datetime
-# type :string(255) default("ProjectHook")
+# type :string default("ProjectHook")
# service_id :integer
# push_events :boolean default(TRUE), not null
# issues_events :boolean default(FALSE), not null
diff --git a/app/models/issue.rb b/app/models/issue.rb
index 7beba98460..5f58c0508f 100644
--- a/app/models/issue.rb
+++ b/app/models/issue.rb
@@ -38,6 +38,7 @@ class Issue < ActiveRecord::Base
scope :cared, ->(user) { where(assignee_id: user) }
scope :open_for, ->(user) { opened.assigned_to(user) }
+ scope :in_projects, ->(project_ids) { where(project_id: project_ids) }
state_machine :state, initial: :opened do
event :close do
diff --git a/app/models/label.rb b/app/models/label.rb
index 220da10a6a..07a1db4abe 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
@@ -85,6 +86,10 @@ class Label < ActiveRecord::Base
issues.opened.count
end
+ def closed_issues_count
+ issues.closed.count
+ end
+
def template?
template
end
diff --git a/app/models/member.rb b/app/models/member.rb
index 34efcd0088..ca08007b7e 100644
--- a/app/models/member.rb
+++ b/app/models/member.rb
@@ -39,7 +39,6 @@ class Member < ActiveRecord::Base
if: :invite?
},
email: {
- strict_mode: true,
allow_nil: true
},
uniqueness: {
diff --git a/app/models/merge_request.rb b/app/models/merge_request.rb
index 0af6064554..1543ef311d 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")
@@ -137,6 +138,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_states(:opened, :reopened) }
scope :merged, -> { with_state(:merged) }
scope :closed, -> { with_state(:closed) }
scope :closed_and_merged, -> { with_states(:closed, :merged) }
@@ -240,7 +242,7 @@ class MergeRequest < ActiveRecord::Base
return unless unchecked?
can_be_merged =
- project.repository.can_be_merged?(source_sha, target_branch)
+ !broken? && project.repository.can_be_merged?(source_sha, target_branch)
if can_be_merged
mark_as_mergeable
@@ -258,7 +260,7 @@ class MergeRequest < ActiveRecord::Base
end
def work_in_progress?
- !!(title =~ /\A\[?WIP\]?:? /i)
+ !!(title =~ /\A\[?WIP(\]|:| )/i)
end
def mergeable?
@@ -284,7 +286,8 @@ class MergeRequest < ActiveRecord::Base
def can_remove_source_branch?(current_user)
!source_project.protected_branch?(source_branch) &&
!source_project.root_ref?(source_branch) &&
- Ability.abilities.allowed?(current_user, :push_code, source_project)
+ Ability.abilities.allowed?(current_user, :push_code, source_project) &&
+ last_commit == source_project.commit(source_branch)
end
def mr_and_commit_notes
@@ -346,10 +349,10 @@ class MergeRequest < ActiveRecord::Base
# Return the set of issues that will be closed if this merge request is accepted.
def closes_issues(current_user = self.author)
if target_branch == project.default_branch
- issues = commits.flat_map { |c| c.closes_issues(current_user) }
- issues.push(*Gitlab::ClosingIssueExtractor.new(project, current_user).
- closed_by_message(description))
- issues.uniq(&:id)
+ messages = commits.map(&:safe_message) << description
+
+ Gitlab::ClosingIssueExtractor.new(project, current_user).
+ closed_by_message(messages.join("\n"))
else
[]
end
@@ -530,4 +533,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/milestone.rb b/app/models/milestone.rb
index c9a0ad8b9b..7dc2f909b2 100644
--- a/app/models/milestone.rb
+++ b/app/models/milestone.rb
@@ -27,6 +27,7 @@ class Milestone < ActiveRecord::Base
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
@@ -34,7 +35,7 @@ class Milestone < ActiveRecord::Base
scope :closed, -> { with_state(:closed) }
scope :of_projects, ->(ids) { where(project_id: ids) }
- validates :title, presence: true
+ validates :title, presence: true, uniqueness: { scope: :project_id }
validates :project, presence: true
strip_attributes :title
@@ -109,6 +110,12 @@ class Milestone < ActiveRecord::Base
0
end
+ def remaining_days
+ return 0 if !due_date || expired?
+
+ (due_date - Date.today).to_i
+ end
+
def expires_at
if due_date
if due_date.past?
diff --git a/app/models/note.rb b/app/models/note.rb
index 605caed9eb..d287e0f3c6 100644
--- a/app/models/note.rb
+++ b/app/models/note.rb
@@ -33,10 +33,12 @@ class Note < ActiveRecord::Base
participant :author
belongs_to :project
- belongs_to :noteable, polymorphic: true
+ belongs_to :noteable, polymorphic: true, touch: true
belongs_to :author, class_name: "User"
belongs_to :updated_by, class_name: "User"
+ has_many :todos, dependent: :destroy
+
delegate :name, to: :project, prefix: true
delegate :name, :email, to: :author, prefix: true
@@ -375,6 +377,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 +385,7 @@ class Note < ActiveRecord::Base
private
def awards_supported?
- noteable.kind_of?(Issue) || noteable.is_a?(MergeRequest)
+ (noteable.kind_of?(Issue) || noteable.is_a?(MergeRequest)) && !for_diff_line?
end
def contains_emoji_only?
diff --git a/app/models/project.rb b/app/models/project.rb
index 9cd2b1af28..8d98ccdf47 100644
--- a/app/models/project.rb
+++ b/app/models/project.rb
@@ -36,6 +36,7 @@
# build_coverage_regex :string
# build_allow_git_fetch :boolean default(TRUE), not null
# build_timeout :integer default(3600), not null
+# pending_delete :boolean
#
require 'carrierwave/orm/activerecord'
@@ -150,6 +151,7 @@ class Project < ActiveRecord::Base
has_many :releases, dependent: :destroy
has_many :lfs_objects_projects, dependent: :destroy
has_many :lfs_objects, through: :lfs_objects_projects
+ has_many :todos, dependent: :destroy
has_one :import_data, dependent: :destroy, class_name: "ProjectImportData"
@@ -272,6 +274,10 @@ class Project < ActiveRecord::Base
query: "%#{query.try(:downcase)}%")
end
+ def search_by_visibility(level)
+ where(visibility_level: Gitlab::VisibilityLevel.const_get(level.upcase))
+ end
+
def search_by_title(query)
where('projects.archived = ?', false).where('LOWER(projects.name) LIKE :query', query: "%#{query.downcase}%")
end
@@ -337,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')
@@ -377,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
@@ -702,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
@@ -730,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
@@ -785,6 +822,8 @@ class Project < ActiveRecord::Base
def change_head(branch)
# Cached divergent commit counts are based on repository head
repository.expire_branch_cache
+ repository.expire_root_ref_cache
+
gitlab_shell.update_repository_head(self.path_with_namespace, branch)
reload_default_branch
end
@@ -905,4 +944,8 @@ class Project < ActiveRecord::Base
def runners_token
ensure_runners_token!
end
+
+ def wiki
+ @wiki ||= ProjectWiki.new(self, self.owner)
+ end
end
diff --git a/app/models/project_services/pushover_service.rb b/app/models/project_services/pushover_service.rb
index 3d7e8bbee6..e76d9eca2a 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 9f380a382c..9629c7e1bb 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 8ce4749597..c96e6f0b8e 100644
--- a/app/models/project_wiki.rb
+++ b/app/models/project_wiki.rb
@@ -12,6 +12,7 @@ class ProjectWiki
# Returns a string describing what went wrong after
# an operation fails.
attr_reader :error_message
+ attr_reader :project
def initialize(project, user = nil)
@project = project
@@ -122,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 e9978c5e66..e050bd4525 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,11 +38,18 @@ 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?
- raw_repository.empty?
+ return @empty unless @empty.nil?
+
+ @empty = cache.fetch(:empty?) { raw_repository.empty? }
end
#
@@ -57,11 +62,15 @@ class Repository
# This method return true if repository contains some content visible in project page.
#
def has_visible_content?
- raw_repository.branch_count > 0
+ return @has_visible_content unless @has_visible_content.nil?
+
+ @has_visible_content = cache.fetch(:has_visible_content?) do
+ raw_repository.branch_count > 0
+ end
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
@@ -78,7 +87,8 @@ class Repository
offset: offset,
# --follow doesn't play well with --skip. See:
# https://gitlab.com/gitlab-org/gitlab-ce/issues/3574#note_3040520
- follow: false
+ follow: false,
+ skip_merges: skip_merges
}
commits = Gitlab::Git::Commit.where(options)
@@ -184,8 +194,11 @@ class Repository
cache.fetch(:"diverging_commit_counts_#{branch.name}") do
# Rugged seems to throw a `ReferenceError` when given branch_names rather
# than SHA-1 hashes
- number_commits_behind = commits_between(branch.target, root_ref_hash).size
- number_commits_ahead = commits_between(root_ref_hash, branch.target).size
+ number_commits_behind = raw_repository.
+ count_commits_between(branch.target, root_ref_hash)
+
+ number_commits_ahead = raw_repository.
+ count_commits_between(root_ref_hash, branch.target)
{ behind: number_commits_behind, ahead: number_commits_ahead }
end
@@ -196,12 +209,6 @@ class Repository
readme version contribution_guide changelog license)
end
- def branch_cache_keys
- branches.map do |branch|
- :"diverging_commit_counts_#{branch.name}"
- end
- end
-
def build_cache
cache_keys.each do |key|
unless cache.exist?(key)
@@ -226,20 +233,60 @@ class Repository
@branches = nil
end
- def expire_cache
+ def expire_cache(branch_name = nil)
cache_keys.each do |key|
cache.expire(key)
end
- expire_branch_cache
+ 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
- branches.each do |branch|
- cache.expire(:"diverging_commit_counts_#{branch.name}")
+ # Expires _all_ caches, including those that would normally only be expired
+ # under specific conditions.
+ def expire_all_caches!
+ expire_cache
+ expire_root_ref_cache
+ expire_emptiness_caches
+ expire_has_visible_content_cache
+ end
+
+ def expire_branch_cache(branch_name = nil)
+ # When we push to the root branch we have to flush the cache for all other
+ # branches as their statistics are based on the commits relative to the
+ # root branch.
+ if !branch_name || branch_name == root_ref
+ branches.each do |branch|
+ cache.expire(:"diverging_commit_counts_#{branch.name}")
+ end
+ # In case a commit is pushed to a non-root branch we only have to flush the
+ # cache for said branch.
+ else
+ cache.expire(:"diverging_commit_counts_#{branch_name}")
end
end
+ def expire_root_ref_cache
+ cache.expire(:root_ref)
+ @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
+ end
+
def rebuild_cache
cache_keys.each do |key|
cache.expire(key)
@@ -477,7 +524,7 @@ class Repository
end
def root_ref
- @root_ref ||= raw_repository.root_ref
+ @root_ref ||= cache.fetch(:root_ref) { raw_repository.root_ref }
end
def commit_dir(user, path, message, branch)
@@ -576,6 +623,34 @@ class Repository
end
end
+ def revert(user, commit, base_branch, target_branch = nil)
+ source_sha = find_branch(base_branch).target
+ target_branch ||= base_branch
+ 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)
+
+ commit_with_hooks(user, target_branch) do |ref|
+ committer = user_to_committer(user)
+ source_sha = Rugged::Commit.create(rugged,
+ message: commit.revert_message,
+ author: committer,
+ committer: committer,
+ tree: tree_id,
+ parents: [rugged.lookup(source_sha)],
+ update_ref: ref)
+ end
+ 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)
@@ -588,6 +663,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
@@ -600,7 +677,7 @@ class Repository
def search_files(query, ref)
offset = 2
- args = %W(#{Gitlab.config.git.bin_path} grep -i -n --before-context #{offset} --after-context #{offset} -e #{query} #{ref || root_ref})
+ args = %W(#{Gitlab.config.git.bin_path} grep -i -I -n --before-context #{offset} --after-context #{offset} -e #{query} #{ref || root_ref})
Gitlab::Popen.popen(args, path_to_repo).first.scrub.split(/^--$/)
end
@@ -651,12 +728,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|
@@ -668,7 +748,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
@@ -683,6 +763,8 @@ class Repository
end
end
end
+
+ newrev
end
end
diff --git a/app/models/spam_log.rb b/app/models/spam_log.rb
new file mode 100644
index 0000000000..12df68ef83
--- /dev/null
+++ b/app/models/spam_log.rb
@@ -0,0 +1,10 @@
+class SpamLog < ActiveRecord::Base
+ belongs_to :user
+
+ validates :user, presence: true
+
+ def remove_user
+ user.block
+ user.destroy
+ end
+end
diff --git a/app/models/spam_report.rb b/app/models/spam_report.rb
new file mode 100644
index 0000000000..cdc7321b08
--- /dev/null
+++ b/app/models/spam_report.rb
@@ -0,0 +1,5 @@
+class SpamReport < ActiveRecord::Base
+ belongs_to :user
+
+ validates :user, presence: true
+end
diff --git a/app/models/todo.rb b/app/models/todo.rb
new file mode 100644
index 0000000000..5f91991f78
--- /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/tree.rb b/app/models/tree.rb
index e0e04d8859..7c4ed6e393 100644
--- a/app/models/tree.rb
+++ b/app/models/tree.rb
@@ -17,12 +17,20 @@ class Tree
def readme
return @readme if defined?(@readme)
- # Take the first previewable readme, or return nil if none is available or
- # we can't preview any of them
- readme_tree = blobs.find do |blob|
- blob.readme? && (previewable?(blob.name) || plain?(blob.name))
+ available_readmes = blobs.select(&:readme?)
+
+ previewable_readmes = available_readmes.select do |blob|
+ previewable?(blob.name)
end
+ plain_readmes = available_readmes.select do |blob|
+ plain?(blob.name)
+ end
+
+ # Prioritize previewable over plain readmes
+ readme_tree = previewable_readmes.first || plain_readmes.first
+
+ # Return if we can't preview any of them
if readme_tree.nil?
return @readme = nil
end
@@ -31,6 +39,8 @@ class Tree
git_repo = repository.raw_repository
@readme = Gitlab::Git::Blob.find(git_repo, sha, readme_path)
+ @readme.load_all_data!(git_repo)
+ @readme
end
def trees
diff --git a/app/models/user.rb b/app/models/user.rb
index 4214f01f6a..b8d4841e65 100644
--- a/app/models/user.rb
+++ b/app/models/user.rb
@@ -138,18 +138,16 @@ class User < ActiveRecord::Base
has_many :assigned_merge_requests, dependent: :destroy, foreign_key: :assignee_id, class_name: "MergeRequest"
has_many :oauth_applications, class_name: 'Doorkeeper::Application', as: :owner, dependent: :destroy
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
#
validates :name, presence: true
- # Note that a 'uniqueness' and presence check is provided by devise :validatable for email. We do not need to
- # duplicate that here as the validation framework will have duplicate errors in the event of a failure.
- validates :email, presence: true, email: { strict_mode: true }
- validates :notification_email, presence: true, email: { strict_mode: true }
- validates :public_email, presence: true, email: { strict_mode: true }, allow_blank: true, uniqueness: true
+ validates :notification_email, presence: true, email: true
+ validates :public_email, presence: true, uniqueness: true, email: true, allow_blank: true
validates :bio, length: { maximum: 255 }, allow_blank: true
validates :projects_limit, presence: true, numericality: { greater_than_or_equal_to: 0 }
validates :username,
@@ -356,11 +354,12 @@ 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
@@ -604,6 +603,13 @@ class User < ActiveRecord::Base
end
end
+ def try_obtain_ldap_lease
+ # After obtaining this lease LDAP checks will be blocked for 600 seconds
+ # (10 minutes) for this user.
+ lease = Gitlab::ExclusiveLease.new("user_ldap_check:#{id}", timeout: 600)
+ lease.try_obtain
+ end
+
def solo_owned_groups
@solo_owned_groups ||= owned_groups.select do |group|
group.owners == [self]
diff --git a/app/models/wiki_page.rb b/app/models/wiki_page.rb
index 2a65f0431c..dbd70dc5a4 100644
--- a/app/models/wiki_page.rb
+++ b/app/models/wiki_page.rb
@@ -110,7 +110,7 @@ class WikiPage
# Returns boolean True or False if this instance
# is an old version of the page.
def historical?
- @page.historical?
+ @page.historical? && versions.first.sha != version.sha
end
# Returns boolean True or False if this instance
diff --git a/app/services/base_service.rb b/app/services/base_service.rb
index b48ca67d4d..8563633816 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 ad901f2da5..002f7ba127 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/ci/image_for_build_service.rb b/app/services/ci/image_for_build_service.rb
index f469b13e90..005a5c4661 100644
--- a/app/services/ci/image_for_build_service.rb
+++ b/app/services/ci/image_for_build_service.rb
@@ -1,28 +1,23 @@
module Ci
class ImageForBuildService
- def execute(project, params)
- sha = params[:sha]
- sha ||=
- if params[:ref]
- project.commit(params[:ref]).try(:sha)
- end
+ def execute(project, opts)
+ sha = opts[:sha] || ref_sha(project, opts[:ref])
commit = project.ci_commits.ordered.find_by(sha: sha)
image_name = image_for_commit(commit)
image_path = Rails.root.join('public/ci', image_name)
-
- OpenStruct.new(
- path: image_path,
- name: image_name
- )
+ OpenStruct.new(path: image_path, name: image_name)
end
private
+ def ref_sha(project, ref)
+ project.commit(ref).try(:sha) if ref
+ end
+
def image_for_commit(commit)
return 'build-unknown.svg' unless commit
-
'build-' + commit.status + ".svg"
end
end
diff --git a/app/services/commits/revert_service.rb b/app/services/commits/revert_service.rb
new file mode 100644
index 0000000000..43d1c766e3
--- /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
+
+ if @create_merge_request
+ # Temporary branch exists and contains the revert commit
+ return success if repository.find_branch(revert_into)
+
+ create_target_branch
+ end
+
+ unless repository.revert(current_user, @commit, revert_into)
+ 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
+
+ success
+ end
+
+ private
+
+ def create_target_branch
+ result = CreateBranchService.new(@project, current_user)
+ .execute(@commit.revert_branch_name, @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/create_branch_service.rb b/app/services/create_branch_service.rb
index c0e08a151f..707c2f7ff8 100644
--- a/app/services/create_branch_service.rb
+++ b/app/services/create_branch_service.rb
@@ -29,11 +29,7 @@ class CreateBranchService < BaseService
end
if new_branch
- push_data = build_push_data(project, current_user, new_branch)
-
- project.execute_hooks(push_data.dup, :push_hooks)
- project.execute_services(push_data.dup, :push_hooks)
-
+ # GitPushService handles execution of services and hooks for branch pushes
success(new_branch)
else
error('Invalid reference name')
diff --git a/app/services/create_spam_log_service.rb b/app/services/create_spam_log_service.rb
new file mode 100644
index 0000000000..59a66fde47
--- /dev/null
+++ b/app/services/create_spam_log_service.rb
@@ -0,0 +1,13 @@
+class CreateSpamLogService < BaseService
+ def initialize(project, user, params)
+ super(project, user, params)
+ end
+
+ def execute
+ spam_params = params.merge({ user_id: @current_user.id,
+ project_id: @project.id } )
+ spam_log = SpamLog.new(spam_params)
+ spam_log.save
+ spam_log
+ end
+end
diff --git a/app/services/delete_branch_service.rb b/app/services/delete_branch_service.rb
index 004b3ce728..fae069ee4a 100644
--- a/app/services/delete_branch_service.rb
+++ b/app/services/delete_branch_service.rb
@@ -25,11 +25,7 @@ class DeleteBranchService < BaseService
end
if repository.rm_branch(current_user, branch_name)
- push_data = build_push_data(branch)
-
- project.execute_hooks(push_data.dup, :push_hooks)
- project.execute_services(push_data.dup, :push_hooks)
-
+ # GitPushService handles execution of services and hooks for branch pushes
success('Branch was removed')
else
error('Failed to remove branch')
diff --git a/app/services/delete_user_service.rb b/app/services/delete_user_service.rb
index e622fd5ea5..173e50c920 100644
--- a/app/services/delete_user_service.rb
+++ b/app/services/delete_user_service.rb
@@ -13,7 +13,7 @@ class DeleteUserService
user.personal_projects.each do |project|
# Skip repository removal because we remove directory with namespace
# that contain all this repositories
- ::Projects::DestroyService.new(project, current_user, skip_repo: true).execute
+ ::Projects::DestroyService.new(project, current_user, skip_repo: true).pending_delete!
end
user.destroy
diff --git a/app/services/destroy_group_service.rb b/app/services/destroy_group_service.rb
index d929a67629..9189de390a 100644
--- a/app/services/destroy_group_service.rb
+++ b/app/services/destroy_group_service.rb
@@ -9,7 +9,7 @@ class DestroyGroupService
@group.projects.each do |project|
# Skip repository removal because we remove directory with namespace
# that contain all this repositories
- ::Projects::DestroyService.new(project, current_user, skip_repo: true).execute
+ ::Projects::DestroyService.new(project, current_user, skip_repo: true).pending_delete!
end
@group.destroy
diff --git a/app/services/git_push_service.rb b/app/services/git_push_service.rb
index d7ea30bc31..a1711d234f 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,62 +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
+ def execute
+ @project.repository.expire_cache(branch_name)
- project.repository.expire_cache
-
- if push_remove_branch?(ref, newrev)
+ if push_remove_branch?
+ @project.repository.expire_has_visible_content_cache
@push_commits = []
- elsif push_to_new_branch?(ref, oldrev)
+ elsif push_to_new_branch?
+ @project.repository.expire_has_visible_content_cache
+
# 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
- branch_name = Gitlab::Git.ref_name(ref)
- 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)
-
- @push_data = build_push_data(oldrev, newrev, ref)
-
- 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)
+ update_merge_requests
end
protected
+ 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
+
+ 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
@@ -89,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
@@ -99,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/issuable_base_service.rb b/app/services/issuable_base_service.rb
index 2556f06e2d..ca87dca4a7 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 a1a20e4768..78254b49af 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 bcb380d321..10787e8873 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 a55a04dd5e..51ef9dfe61 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 a9b29f9654..c0700d953d 100644
--- a/app/services/merge_requests/build_service.rb
+++ b/app/services/merge_requests/build_service.rb
@@ -56,7 +56,7 @@ module MergeRequests
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 47454f9f0c..27ee81fe3e 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 009d5a6867..33609d01f2 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 e8bef250d8..9a58383b39 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 5cf7404a49..531bbc9b06 100644
--- a/app/services/merge_requests/merge_when_build_succeeds_service.rb
+++ b/app/services/merge_requests/merge_when_build_succeeds_service.rb
@@ -19,8 +19,8 @@ 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?
@@ -45,9 +45,14 @@ 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)
+
+ 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 5ff2cc03dd..6319ad805b 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 a8486e6a5a..2bb312bb25 100644
--- a/app/services/notes/create_service.rb
+++ b/app/services/notes/create_service.rb
@@ -6,27 +6,12 @@ module Notes
note.system = false
if note.save
- notification_service.new_note(note)
-
- # Skip system notes, like status changes and cross-references and awards
- unless note.system || note.is_award
- event_service.leave_note(note, note.author)
- note.create_cross_references!
- execute_hooks(note)
- end
+ # 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
-
- def hook_data(note)
- Gitlab::NoteDataBuilder.build(note, current_user)
- end
-
- def execute_hooks(note)
- note_data = hook_data(note)
- 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/post_process_service.rb b/app/services/notes/post_process_service.rb
new file mode 100644
index 0000000000..e818f58d13
--- /dev/null
+++ b/app/services/notes/post_process_service.rb
@@ -0,0 +1,28 @@
+module Notes
+ class PostProcessService
+ attr_accessor :note
+
+ def initialize(note)
+ @note = note
+ end
+
+ def execute
+ # Skip system notes, like status changes and cross-references and awards
+ unless @note.system || @note.is_award
+ EventCreateService.new.leave_note(@note, @note.author)
+ @note.create_cross_references!
+ execute_note_hooks
+ end
+ end
+
+ def hook_data
+ Gitlab::NoteDataBuilder.build(@note, @note.author)
+ end
+
+ def execute_note_hooks
+ note_data = hook_data
+ @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 72e2f78008..1361b1e030 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 28872c8925..f4dcb14285 100644
--- a/app/services/projects/destroy_service.rb
+++ b/app/services/projects/destroy_service.rb
@@ -6,15 +6,25 @@ module Projects
DELETED_FLAG = '+deleted'
+ def pending_delete!
+ project.update_attribute(:pending_delete, true)
+
+ ProjectDestroyWorker.perform_in(1.minute, project.id, current_user.id, params)
+ end
+
def execute
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!
@@ -64,5 +74,13 @@ module Projects
def removal_path(path)
"#{path}+#{project.id}#{DELETED_FLAG}"
end
+
+ def flush_caches(project, wiki_path)
+ project.repository.expire_all_caches! if project.repository.exists?
+
+ wiki_repo = Repository.new(wiki_path, project)
+
+ wiki_repo.expire_all_caches! if wiki_repo.exists?
+ end
end
end
diff --git a/app/services/projects/import_service.rb b/app/services/projects/import_service.rb
new file mode 100644
index 0000000000..2015897dd1
--- /dev/null
+++ b/app/services/projects/import_service.rb
@@ -0,0 +1,67 @@
+module Projects
+ class ImportService < BaseService
+ include Gitlab::ShellAdapter
+
+ class Error < StandardError; end
+
+ ALLOWED_TYPES = [
+ 'bitbucket',
+ 'fogbugz',
+ 'gitlab',
+ 'github',
+ 'google_code'
+ ]
+
+ def execute
+ if unknown_url?
+ # In this case, we only want to import issues, not a repository.
+ create_repository
+ else
+ import_repository
+ end
+
+ import_data
+
+ success
+ rescue Error => e
+ error(e.message)
+ end
+
+ private
+
+ def create_repository
+ unless project.create_repository
+ raise Error, 'The repository could not be created.'
+ end
+ end
+
+ def import_repository
+ begin
+ gitlab_shell.import_repository(project.path_with_namespace, project.import_url)
+ rescue Gitlab::Shell::Error => e
+ raise Error, e.message
+ end
+ end
+
+ def import_data
+ return unless has_importer?
+
+ unless importer.execute
+ raise Error, 'The remote data could not be imported.'
+ end
+ end
+
+ def has_importer?
+ ALLOWED_TYPES.include?(project.import_type)
+ end
+
+ def importer
+ class_name = "Gitlab::#{project.import_type.camelize}Import::Importer"
+ class_name.constantize.new(project)
+ end
+
+ def unknown_url?
+ project.import_url == Project::UNKNOWN_IMPORT_URL
+ end
+ end
+end
diff --git a/app/services/system_note_service.rb b/app/services/system_note_service.rb
index 1083bcec05..edced01081 100644
--- a/app/services/system_note_service.rb
+++ b/app/services/system_note_service.rb
@@ -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 0000000000..4392e2d17f
--- /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/validators/email_validator.rb b/app/validators/email_validator.rb
index b35af10080..aab07a7ece 100644
--- a/app/validators/email_validator.rb
+++ b/app/validators/email_validator.rb
@@ -1,18 +1,5 @@
-# EmailValidator
-#
-# Based on https://github.com/balexand/email_validator
-#
-# Extended to use only strict mode with following allowed characters:
-# ' - apostrophe
-#
-# See http://www.remote.org/jochen/mail/info/chars.html
-#
class EmailValidator < ActiveModel::EachValidator
- PATTERN = /\A\s*([-a-z0-9+._']{1,64})@((?:[-a-z0-9]+\.)+[a-z]{2,})\s*\z/i.freeze
-
def validate_each(record, attribute, value)
- unless value =~ PATTERN
- record.errors.add(attribute, options[:message] || :invalid)
- end
+ record.errors.add(attribute, :invalid) unless value =~ Devise.email_regexp
end
end
diff --git a/app/views/admin/appearances/_form.html.haml b/app/views/admin/appearances/_form.html.haml
new file mode 100644
index 0000000000..6f325914d1
--- /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 0000000000..dd4a64e80b
--- /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 0000000000..089e8e4cb7
--- /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/application_settings/_form.html.haml b/app/views/admin/application_settings/_form.html.haml
index c4020c8273..b30dfd109e 100644
--- a/app/views/admin/application_settings/_form.html.haml
+++ b/app/views/admin/application_settings/_form.html.haml
@@ -14,11 +14,11 @@
.form-group.project-visibility-level-holder
= f.label :default_project_visibility, class: 'control-label col-sm-2'
.col-sm-10
- = render('shared/visibility_radios', model_method: :default_project_visibility, form: f, selected_level: @application_setting.default_project_visibility, form_model: Project)
+ = render('shared/visibility_radios', model_method: :default_project_visibility, form: f, selected_level: @application_setting.default_project_visibility, form_model: Project.new)
.form-group.project-visibility-level-holder
= f.label :default_snippet_visibility, class: 'control-label col-sm-2'
.col-sm-10
- = render('shared/visibility_radios', model_method: :default_snippet_visibility, form: f, selected_level: @application_setting.default_snippet_visibility, form_model: PersonalSnippet)
+ = render('shared/visibility_radios', model_method: :default_snippet_visibility, form: f, selected_level: @application_setting.default_snippet_visibility, form_model: ProjectSnippet.new)
.form-group
= f.label :restricted_visibility_levels, class: 'control-label col-sm-2'
.col-sm-10
@@ -47,6 +47,16 @@
= f.label :version_check_enabled do
= f.check_box :version_check_enabled
Version check enabled
+ .form-group
+ .col-sm-offset-2.col-sm-10
+ .checkbox
+ = f.label :email_author_in_body do
+ = f.check_box :email_author_in_body
+ Include author name in notification email body
+ .help-block
+ Some email servers do not support overriding the email sender name.
+ Enable this option to include the name of the author of the issue,
+ merge request or comment in the email body instead.
.form-group
= f.label :admin_notification_email, class: 'control-label col-sm-2'
.col-sm-10
@@ -105,14 +115,14 @@
= f.check_box :signin_enabled
Sign-in enabled
.form-group
- = f.label :two_factor_authentication, 'Two-Factor authentication', class: 'control-label col-sm-2'
+ = f.label :two_factor_authentication, 'Two-factor authentication', class: 'control-label col-sm-2'
.col-sm-10
.checkbox
= f.label :require_two_factor_authentication do
= f.check_box :require_two_factor_authentication
- Require all users to setup Two-Factor authentication
+ Require all users to setup Two-factor authentication
.form-group
- = f.label :two_factor_authentication, 'Two-Factor grace period (hours)', class: 'control-label col-sm-2'
+ = f.label :two_factor_authentication, 'Two-factor grace period (hours)', class: 'control-label col-sm-2'
.col-sm-10
= f.number_field :two_factor_grace_period, min: 0, class: 'form-control', placeholder: '0'
.help-block Amount of time (in hours) that users are allowed to skip forced configuration of two-factor authentication
@@ -212,42 +222,43 @@
%fieldset
%legend Spam and Anti-bot Protection
- .form-group
- .col-sm-offset-2.col-sm-10
- .checkbox
- = f.label :ip_blocking_enabled do
- = f.check_box :ip_blocking_enabled
- Enable IP check against blacklist at sign-up
- .help-block Helps preventing accounts creation from 'known spam sources'
-
- .form-group
- = f.label :dnsbl_servers_list, class: 'control-label col-sm-2' do
- DNSBL servers list
- .col-sm-10
- = f.text_field :dnsbl_servers_list, class: 'form-control'
- .help-block
- Please enter DNSBL servers separated with comma
-
.form-group
.col-sm-offset-2.col-sm-10
.checkbox
= f.label :recaptcha_enabled do
= f.check_box :recaptcha_enabled
Enable reCAPTCHA
- %span.help-block#recaptcha_help_block Helps preventing bots from creating accounts
+ %span.help-block#recaptcha_help_block Helps prevent bots from creating accounts
.form-group
= f.label :recaptcha_site_key, 'reCAPTCHA Site Key', class: 'control-label col-sm-2'
.col-sm-10
= f.text_field :recaptcha_site_key, class: 'form-control'
.help-block
- Generate site and private keys here:
- %a{ href: 'http://www.google.com/recaptcha', target: '_blank'} http://www.google.com/recaptcha
+ Generate site and private keys at
+ %a{ href: 'http://www.google.com/recaptcha', target: 'blank'} http://www.google.com/recaptcha
+
.form-group
= f.label :recaptcha_private_key, 'reCAPTCHA Private Key', class: 'control-label col-sm-2'
.col-sm-10
= f.text_field :recaptcha_private_key, class: 'form-control'
+ .form-group
+ .col-sm-offset-2.col-sm-10
+ .checkbox
+ = f.label :akismet_enabled do
+ = f.check_box :akismet_enabled
+ Enable Akismet
+ %span.help-block#akismet_help_block Helps prevent bots from creating issues
+
+ .form-group
+ = f.label :akismet_api_key, 'Akismet API Key', class: 'control-label col-sm-2'
+ .col-sm-10
+ = f.text_field :akismet_api_key, class: 'form-control'
+ .help-block
+ Generate API key at
+ %a{ href: 'http://www.akismet.com', target: 'blank'} http://www.akismet.com
+
%fieldset
%legend Error Reporting and Logging
%p
@@ -268,4 +279,4 @@
= f.text_field :sentry_dsn, class: 'form-control'
.form-actions
- = f.submit 'Save', class: 'btn btn-primary'
+ = f.submit 'Save', class: 'btn btn-save'
diff --git a/app/views/admin/applications/_form.html.haml b/app/views/admin/applications/_form.html.haml
index fa4e6335c7..e18f7b499d 100644
--- a/app/views/admin/applications/_form.html.haml
+++ b/app/views/admin/applications/_form.html.haml
@@ -22,5 +22,5 @@
%code= Doorkeeper.configuration.native_redirect_uri
for local tests
.form-actions
- = f.submit 'Submit', class: "btn btn-primary wide"
+ = f.submit 'Submit', class: "btn btn-save wide"
= link_to "Cancel", admin_applications_path, class: "btn btn-default"
diff --git a/app/views/admin/broadcast_messages/_form.html.haml b/app/views/admin/broadcast_messages/_form.html.haml
index 953b8b6936..5c9403fa0c 100644
--- a/app/views/admin/broadcast_messages/_form.html.haml
+++ b/app/views/admin/broadcast_messages/_form.html.haml
@@ -1,6 +1,7 @@
.broadcast-message-preview{ style: broadcast_message_style(@broadcast_message) }
= icon('bullhorn')
- %span= @broadcast_message.message || "Your message here"
+ .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|
-if @broadcast_message.errors.any?
@@ -10,7 +11,9 @@
.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
+ = f.text_area :message, class: "form-control js-quick-submit js-autosize",
+ required: true,
+ data: { preview_path: preview_admin_broadcast_messages_path }
.form-group.js-toggle-colors-container
.col-sm-10.col-sm-offset-2
= link_to 'Customize colors', '#', class: 'js-toggle-colors-link'
diff --git a/app/views/admin/broadcast_messages/index.html.haml b/app/views/admin/broadcast_messages/index.html.haml
index 49e33698b6..c05538a393 100644
--- a/app/views/admin/broadcast_messages/index.html.haml
+++ b/app/views/admin/broadcast_messages/index.html.haml
@@ -34,4 +34,4 @@
= link_to icon('pencil-square-o'), edit_admin_broadcast_message_path(message), title: 'Edit', class: 'btn btn-xs'
= link_to icon('times'), admin_broadcast_message_path(message), method: :delete, remote: true, title: 'Remove', class: 'js-remove-tr btn btn-xs btn-danger'
- = paginate @broadcast_messages
+ = paginate @broadcast_messages, theme: 'gitlab'
diff --git a/app/views/admin/broadcast_messages/preview.js.haml b/app/views/admin/broadcast_messages/preview.js.haml
new file mode 100644
index 0000000000..fbc9453c72
--- /dev/null
+++ b/app/views/admin/broadcast_messages/preview.js.haml
@@ -0,0 +1 @@
+$('.js-broadcast-message-preview').html("#{j(render_broadcast_message(@message))}");
diff --git a/app/views/admin/builds/_build.html.haml b/app/views/admin/builds/_build.html.haml
index c395bd908c..34d955568f 100644
--- a/app/views/admin/builds/_build.html.haml
+++ b/app/views/admin/builds/_build.html.haml
@@ -4,7 +4,7 @@
= ci_status_with_icon(build.status)
%td.build-link
- - if build.target_url
+ - if can?(current_user, :read_build, project) && build.target_url
= link_to build.target_url do
%strong Build ##{build.id}
- else
@@ -60,10 +60,10 @@
%td
.pull-right
- - if current_user && can?(current_user, :read_build_artifacts, project) && build.artifacts?
+ - if can?(current_user, :read_build, project) && build.artifacts?
= link_to build.artifacts_download_url, title: 'Download artifacts' do
%i.fa.fa-download
- - if current_user && can?(current_user, :manage_builds, build.project)
+ - if can?(current_user, :update_build, build.project)
- if build.active?
- if build.cancel_url
= link_to build.cancel_url, method: :post, title: 'Cancel' do
diff --git a/app/views/admin/builds/index.html.haml b/app/views/admin/builds/index.html.haml
index ebf2b7b60e..5931efdefe 100644
--- a/app/views/admin/builds/index.html.haml
+++ b/app/views/admin/builds/index.html.haml
@@ -1,9 +1,4 @@
-.project-issuable-filter
- .controls
- .pull-left.hidden-xs
- - if @all_builds.running_or_pending.any?
- = link_to 'Cancel all', cancel_all_admin_builds_path, data: { confirm: 'Are you sure?' }, class: 'btn btn-danger', method: :post
-
+.top-area
%ul.nav-links
%li{class: ('active' if @scope.nil?)}
= link_to admin_builds_path do
@@ -20,7 +15,11 @@
Finished
%span.badge.js-running-count= number_with_delimiter(@all_builds.finished.count(:id))
-.gray-content-block
+ .nav-controls
+ - if @all_builds.running_or_pending.any?
+ = link_to 'Cancel all', cancel_all_admin_builds_path, data: { confirm: 'Are you sure?' }, class: 'btn btn-danger', method: :post
+
+.gray-content-block.second-block
#{(@scope || 'running').capitalize} builds
%ul.content-list
diff --git a/app/views/admin/dashboard/index.html.haml b/app/views/admin/dashboard/index.html.haml
index cc389c3ae0..3274ba5377 100644
--- a/app/views/admin/dashboard/index.html.haml
+++ b/app/views/admin/dashboard/index.html.haml
@@ -92,6 +92,11 @@
Rails
%span.pull-right
#{Rails::VERSION::STRING}
+
+ %p
+ = Gitlab::Database.adapter_name
+ %span.pull-right
+ = Gitlab::Database.version
%hr
.row
.col-sm-4
diff --git a/app/views/admin/deploy_keys/index.html.haml b/app/views/admin/deploy_keys/index.html.haml
index 841e6971fb..41c4389997 100644
--- a/app/views/admin/deploy_keys/index.html.haml
+++ b/app/views/admin/deploy_keys/index.html.haml
@@ -2,7 +2,7 @@
.panel.panel-default
.panel-heading
Public deploy keys (#{@deploy_keys.count})
- .panel-head-actions
+ .controls
= link_to 'New Deploy Key', new_admin_deploy_key_path, class: "btn btn-new btn-sm"
- if @deploy_keys.any?
.table-holder
diff --git a/app/views/admin/groups/_form.html.haml b/app/views/admin/groups/_form.html.haml
index 8de2ba74a7..198026a1f7 100644
--- a/app/views/admin/groups/_form.html.haml
+++ b/app/views/admin/groups/_form.html.haml
@@ -21,6 +21,5 @@
- else
.form-actions
- = f.submit 'Save changes', class: "btn btn-primary"
+ = f.submit 'Save changes', class: "btn btn-save"
= link_to 'Cancel', admin_group_path(@group), class: "btn btn-cancel"
-
diff --git a/app/views/admin/groups/index.html.haml b/app/views/admin/groups/index.html.haml
index 3940210e19..118d3cfea0 100644
--- a/app/views/admin/groups/index.html.haml
+++ b/app/views/admin/groups/index.html.haml
@@ -17,7 +17,7 @@
.pull-right
.dropdown.inline
%a.dropdown-toggle.btn{href: '#', "data-toggle" => "dropdown"}
- %span.light sort:
+ %span.light
- if @sort.present?
= sort_options_hash[@sort]
- else
diff --git a/app/views/admin/hooks/index.html.haml b/app/views/admin/hooks/index.html.haml
index b120f4dea6..53b3cd04c6 100644
--- a/app/views/admin/hooks/index.html.haml
+++ b/app/views/admin/hooks/index.html.haml
@@ -37,8 +37,7 @@
- @hooks.each do |hook|
%li
.list-item-name
- = link_to admin_hook_path(hook) do
- %strong= hook.url
+ %strong= hook.url
%p SSL Verification: #{hook.enable_ssl_verification ? "enabled" : "disabled"}
.pull-right
diff --git a/app/views/admin/labels/_form.html.haml b/app/views/admin/labels/_form.html.haml
index eaa94ed9e3..8c6b389bf1 100644
--- a/app/views/admin/labels/_form.html.haml
+++ b/app/views/admin/labels/_form.html.haml
@@ -11,6 +11,10 @@
= f.label :title, class: 'control-label'
.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
diff --git a/app/views/admin/labels/_label.html.haml b/app/views/admin/labels/_label.html.haml
index e3ccbf6c3a..5736a30191 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/projects/index.html.haml b/app/views/admin/projects/index.html.haml
index d9b481404f..d39c0f4403 100644
--- a/app/views/admin/projects/index.html.haml
+++ b/app/views/admin/projects/index.html.haml
@@ -1,7 +1,7 @@
- page_title "Projects"
= render 'shared/show_aside'
-.row
+.row.prepend-top-default
%aside.col-md-3
.admin-filter
= form_tag admin_namespaces_projects_path, method: :get, class: '' do
@@ -47,10 +47,10 @@
.panel.panel-default
.panel-heading
Projects (#{@projects.total_count})
- .panel-head-actions
+ .controls
.dropdown.inline
%button.dropdown-toggle.btn.btn-sm{type: 'button', 'data-toggle' => 'dropdown'}
- %span.light sort:
+ %span.light
- if @sort.present?
= sort_options_hash[@sort]
- else
diff --git a/app/views/admin/spam_logs/_spam_log.html.haml b/app/views/admin/spam_logs/_spam_log.html.haml
new file mode 100644
index 0000000000..8aea67f449
--- /dev/null
+++ b/app/views/admin/spam_logs/_spam_log.html.haml
@@ -0,0 +1,32 @@
+- user = spam_log.user
+%tr
+ %td
+ = time_ago_with_tooltip(spam_log.created_at)
+ %td
+ - if user
+ = link_to user.name, [:admin, user]
+ .light.small
+ Joined #{time_ago_with_tooltip(user.created_at)}
+ - else
+ (removed)
+ %td
+ = spam_log.source_ip
+ %td
+ = spam_log.via_api? ? 'Y' : 'N'
+ %td
+ = spam_log.noteable_type
+ %td
+ = spam_log.title
+ %td
+ = truncate(spam_log.description, length: 100)
+ %td
+ - if user
+ = link_to 'Remove user', admin_spam_log_path(spam_log, remove_user: true),
+ data: { confirm: "USER #{user.name} WILL BE REMOVED! Are you sure?" }, method: :delete, class: "btn btn-xs btn-remove"
+ %td
+ - if user && !user.blocked?
+ = link_to 'Block user', block_admin_user_path(user), data: {confirm: 'USER WILL BE BLOCKED! Are you sure?'}, method: :put, class: "btn btn-xs"
+ - else
+ .btn.btn-xs.disabled
+ Already Blocked
+ = link_to 'Remove log', [:admin, spam_log], remote: true, method: :delete, class: "btn btn-xs btn-close js-remove-tr"
diff --git a/app/views/admin/spam_logs/index.html.haml b/app/views/admin/spam_logs/index.html.haml
new file mode 100644
index 0000000000..0fdd5bd996
--- /dev/null
+++ b/app/views/admin/spam_logs/index.html.haml
@@ -0,0 +1,21 @@
+- page_title "Spam Logs"
+%h3.page-title Spam Logs
+%hr
+- if @spam_logs.present?
+ .table-holder
+ %table.table
+ %thead
+ %tr
+ %th Date
+ %th User
+ %th Source IP
+ %th API?
+ %th Type
+ %th Title
+ %th Description
+ %th Primary Action
+ %th
+ = render @spam_logs
+ = paginate @spam_logs
+- else
+ %h4 There are no Spam Logs
diff --git a/app/views/admin/users/index.html.haml b/app/views/admin/users/index.html.haml
index b050a4d01c..b6b1168bd3 100644
--- a/app/views/admin/users/index.html.haml
+++ b/app/views/admin/users/index.html.haml
@@ -32,7 +32,7 @@
.pull-right
.dropdown.inline
%a.dropdown-toggle.btn{href: '#', "data-toggle" => "dropdown"}
- %span.light sort:
+ %span.light
- if @sort.present?
= sort_options_hash[@sort]
- else
diff --git a/app/views/dashboard/_groups_head.html.haml b/app/views/dashboard/_groups_head.html.haml
index 6ca97a692b..3d17f74b70 100644
--- a/app/views/dashboard/_groups_head.html.haml
+++ b/app/views/dashboard/_groups_head.html.haml
@@ -1,7 +1,13 @@
-%ul.nav-links
- = nav_link(page: dashboard_groups_path) do
- = link_to dashboard_groups_path, title: 'Your groups', data: {placement: 'right'} do
- Your Groups
- = nav_link(page: explore_groups_path) do
- = link_to explore_groups_path, title: 'Explore groups', data: {placement: 'bottom'} do
- Explore Groups
+.top-area
+ %ul.nav-links
+ = nav_link(page: dashboard_groups_path) do
+ = link_to dashboard_groups_path, title: 'Your groups' do
+ Your Groups
+ = nav_link(page: explore_groups_path) do
+ = link_to explore_groups_path, title: 'Explore groups' do
+ Explore Groups
+ - if current_user.can_create_group?
+ .nav-controls
+ = link_to new_group_path, class: "btn btn-new" do
+ = icon('plus')
+ New Group
diff --git a/app/views/dashboard/_projects_head.html.haml b/app/views/dashboard/_projects_head.html.haml
index 5c4b58cd68..4bc761b373 100644
--- a/app/views/dashboard/_projects_head.html.haml
+++ b/app/views/dashboard/_projects_head.html.haml
@@ -8,13 +8,15 @@
= nav_link(page: starred_dashboard_projects_path) do
= link_to starred_dashboard_projects_path, title: 'Starred Projects', data: {placement: 'right'} do
Starred Projects
- = nav_link(page: [explore_root_path, trending_explore_projects_path, starred_explore_projects_path, explore_projects_path], html_options: { class: 'hidden-xs' }) do
+ = nav_link(page: [explore_root_path, trending_explore_projects_path, starred_explore_projects_path, explore_projects_path]) do
= link_to explore_root_path, title: 'Explore', data: {placement: 'right'} do
Explore Projects
- .projects-search-form
- = search_field_tag :filter_projects, nil, placeholder: 'Filter by name...', class: 'projects-list-filter form-control hidden-xs', spellcheck: false
+ .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'
+ = render 'explore/projects/dropdown'
- if current_user.can_create_project?
- = link_to new_project_path, class: 'btn btn-green' do
- %i.fa.fa-plus
+ = link_to new_project_path, class: 'btn btn-new' do
+ = icon('plus')
New Project
diff --git a/app/views/dashboard/groups/index.html.haml b/app/views/dashboard/groups/index.html.haml
index d5b7e729e7..caca91af53 100644
--- a/app/views/dashboard/groups/index.html.haml
+++ b/app/views/dashboard/groups/index.html.haml
@@ -2,15 +2,6 @@
- header_title "Groups", dashboard_groups_path
= render 'dashboard/groups_head'
-.gray-content-block
- - if current_user.can_create_group?
- %span.pull-right.hidden-xs
- = link_to new_group_path, class: "btn btn-new" do
- %i.fa.fa-plus
- New Group
- .oneline
- Group members have access to all group projects.
-
%ul.content-list
- @group_members.each do |group_member|
- group = group_member.group
diff --git a/app/views/dashboard/issues.html.haml b/app/views/dashboard/issues.html.haml
index 2d3da01178..dfa5f80eef 100644
--- a/app/views/dashboard/issues.html.haml
+++ b/app/views/dashboard/issues.html.haml
@@ -4,20 +4,15 @@
- if current_user
= auto_discovery_link_tag(:atom, issues_dashboard_url(format: :atom, private_token: current_user.private_token), title: "#{current_user.name} issues")
-.project-issuable-filter
- .controls
- .pull-left
- - if current_user
- .hidden-xs.pull-left
- = link_to issues_dashboard_url(format: :atom, private_token: current_user.private_token), class: 'btn' do
- %i.fa.fa-rss
-
+.top-area
+ = render 'shared/issuable/nav', type: :issues
+ .nav-controls
+ - if current_user
+ = link_to issues_dashboard_url(format: :atom, private_token: current_user.private_token), class: 'btn' do
+ = icon('rss')
= render 'shared/new_project_item_select', path: 'issues/new', label: "New Issue"
- = render 'shared/issuable/filter', type: :issues
-
-.gray-content-block.second-block
- List all issues from all projects you have access to.
+= render 'shared/issuable/filter', type: :issues
.prepend-top-default
= render 'shared/issues'
diff --git a/app/views/dashboard/merge_requests.html.haml b/app/views/dashboard/merge_requests.html.haml
index c5a5ec21f7..fb016599fe 100644
--- a/app/views/dashboard/merge_requests.html.haml
+++ b/app/views/dashboard/merge_requests.html.haml
@@ -1,14 +1,12 @@
- page_title "Merge Requests"
- header_title "Merge Requests", merge_requests_dashboard_path(assignee_id: current_user.id)
-.project-issuable-filter
- .controls
+.top-area
+ = render 'shared/issuable/nav', type: :merge_requests
+ .nav-controls
= render 'shared/new_project_item_select', path: 'merge_requests/new', label: "New Merge Request"
- = render 'shared/issuable/filter', type: :merge_requests
-
-.gray-content-block.second-block
- List all merge requests from all projects you have access to.
+= render 'shared/issuable/filter', type: :merge_requests
.prepend-top-default
= render 'shared/merge_requests'
diff --git a/app/views/dashboard/milestones/index.html.haml b/app/views/dashboard/milestones/index.html.haml
index bec1692a4d..917bfbd47e 100644
--- a/app/views/dashboard/milestones/index.html.haml
+++ b/app/views/dashboard/milestones/index.html.haml
@@ -1,14 +1,11 @@
- page_title "Milestones"
- header_title "Milestones", dashboard_milestones_path
-.project-issuable-filter
- .controls
- = render 'shared/new_project_item_select', path: 'milestones/new', label: "New Milestone", include_groups: true
-
+.top-area
= render 'shared/milestones_filter'
-.gray-content-block
- List all milestones from all projects you have access to.
+ .nav-controls
+ = render 'shared/new_project_item_select', path: 'milestones/new', label: "New Milestone", include_groups: true
.milestones
%ul.content-list
diff --git a/app/views/dashboard/projects/_projects.html.haml b/app/views/dashboard/projects/_projects.html.haml
index cea9ffcc74..933a3edd0f 100644
--- a/app/views/dashboard/projects/_projects.html.haml
+++ b/app/views/dashboard/projects/_projects.html.haml
@@ -1,3 +1,6 @@
.projects-list-holder
= render 'shared/projects/list', projects: @projects, ci: true
+
+ :javascript
+ Dashboard.init()
diff --git a/app/views/dashboard/projects/index.atom.builder b/app/views/dashboard/projects/index.atom.builder
index 2e2712c514..d4daf07c6c 100644
--- a/app/views/dashboard/projects/index.atom.builder
+++ b/app/views/dashboard/projects/index.atom.builder
@@ -4,7 +4,7 @@ xml.feed "xmlns" => "http://www.w3.org/2005/Atom", "xmlns:media" => "http://sear
xml.link href: dashboard_projects_url(format: :atom, private_token: current_user.try(:private_token)), rel: "self", type: "application/atom+xml"
xml.link href: dashboard_projects_url, rel: "alternate", type: "text/html"
xml.id dashboard_projects_url
- xml.updated @events.latest_update_time.xmlschema if @events.any?
+ xml.updated @events[0].updated_at.xmlschema if @events[0]
@events.each do |event|
event_to_atom(xml, event)
diff --git a/app/views/dashboard/projects/index.html.haml b/app/views/dashboard/projects/index.html.haml
index 53abf274bd..4565e752c1 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 0000000000..f38caf1826
--- /dev/null
+++ b/app/views/dashboard/todos/_todo.html.haml
@@ -0,0 +1,24 @@
+%li{class: "todo todo-#{todo.done? ? 'done' : 'pending'}", id: dom_id(todo) }
+ .todo-item{class: 'todo-block'}
+ = image_tag avatar_icon(todo.author_email, 40), class: 'avatar s40', alt:''
+
+ .todo-title
+ %span.author-name
+ - if todo.author
+ = link_to_author(todo)
+ - else
+ (removed)
+ %span.todo-label
+ = todo_action_name(todo)
+ = todo_target_link(todo)
+
+ · #{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 0000000000..946d7df393
--- /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 dbc8eda619..d65fa60025 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/doorkeeper/authorizations/new.html.haml b/app/views/doorkeeper/authorizations/new.html.haml
index 15f9ee266c..eae80e5210 100644
--- a/app/views/doorkeeper/authorizations/new.html.haml
+++ b/app/views/doorkeeper/authorizations/new.html.haml
@@ -4,6 +4,15 @@
Authorize
%strong.text-info= @pre_auth.client.name
to use your account?
+
+ - if current_user.admin?
+ .text-warning.prepend-top-20
+ %p
+ = icon("exclamation-triangle fw")
+ You are an admin, which means granting access to
+ %strong #{@pre_auth.client.name}
+ will allow them to interact with GitLab as an admin as well. Proceed with caution.
+
- if @pre_auth.scopes
#oauth-permissions
%p This application will be able to:
@@ -25,4 +34,4 @@
= hidden_field_tag :state, @pre_auth.state
= hidden_field_tag :response_type, @pre_auth.response_type
= hidden_field_tag :scope, @pre_auth.scope
- = submit_tag "Deny", class: "btn btn-danger prepend-left-10"
\ No newline at end of file
+ = submit_tag "Deny", class: "btn btn-danger prepend-left-10"
diff --git a/app/views/events/_event.html.haml b/app/views/events/_event.html.haml
index 46432a9234..36fb2d5162 100644
--- a/app/views/events/_event.html.haml
+++ b/app/views/events/_event.html.haml
@@ -3,8 +3,8 @@
.event-item-timestamp
#{time_ago_with_tooltip(event.created_at)}
- = cache [event, current_application_settings, "v2.1"] do
- = image_tag avatar_icon(event.author_email, 46), class: "avatar s46", alt:''
+ = cache [event, current_application_settings, "v2.2"] do
+ = image_tag avatar_icon(event.author_email, 40), class: "avatar s40", alt:''
- if event.created_project?
= render "events/event/created_project", event: event
- elsif event.push?
diff --git a/app/views/explore/groups/index.html.haml b/app/views/explore/groups/index.html.haml
index fcb07b0408..8ffca96bb4 100644
--- a/app/views/explore/groups/index.html.haml
+++ b/app/views/explore/groups/index.html.haml
@@ -18,7 +18,7 @@
.pull-right
.dropdown.inline
%button.dropdown-toggle.btn{type: 'button', 'data-toggle' => 'dropdown'}
- %span.light sort:
+ %span.light
- if @sort.present?
= sort_options_hash[@sort]
- else
diff --git a/app/views/explore/projects/_dropdown.html.haml b/app/views/explore/projects/_dropdown.html.haml
index b23a3c1e5c..87c556adc7 100644
--- a/app/views/explore/projects/_dropdown.html.haml
+++ b/app/views/explore/projects/_dropdown.html.haml
@@ -1,21 +1,15 @@
.dropdown.inline
%button.dropdown-toggle.btn{type: 'button', 'data-toggle' => 'dropdown'}
- %span.light sort:
+ %span.light
- if @sort.present?
= sort_options_hash[@sort]
- - elsif current_page?(trending_explore_projects_path) || current_page?(explore_root_path)
- Trending projects
- - elsif current_page?(starred_explore_projects_path)
- Most stars
- else
- = sort_title_recently_created
+ = sort_title_recently_updated
%b.caret
%ul.dropdown-menu
%li
- = link_to trending_explore_projects_path do
- Trending projects
- = link_to starred_explore_projects_path do
- Most stars
+ = link_to explore_projects_filter_path(sort: sort_value_name) do
+ = sort_title_name
= link_to explore_projects_filter_path(sort: sort_value_recently_created) do
= sort_title_recently_created
= link_to explore_projects_filter_path(sort: sort_value_oldest_created) do
@@ -24,4 +18,3 @@
= sort_title_recently_updated
= link_to explore_projects_filter_path(sort: sort_value_oldest_updated) do
= sort_title_oldest_updated
-
diff --git a/app/views/explore/projects/_filter.html.haml b/app/views/explore/projects/_filter.html.haml
index 28b12c8dca..c248dbb695 100644
--- a/app/views/explore/projects/_filter.html.haml
+++ b/app/views/explore/projects/_filter.html.haml
@@ -1,10 +1,3 @@
-.pull-left
- = form_tag explore_projects_filter_path, method: :get, class: 'form-inline form-tiny' do |f|
- .form-group
- = search_field_tag :search, params[:search], placeholder: "Filter by name", class: "form-control search-text-input", id: "projects_search", spellcheck: false
- .form-group
- = button_tag 'Search', class: "btn"
-
.pull-right.hidden-sm.hidden-xs
- if current_user
.dropdown.inline.append-right-10
@@ -46,4 +39,3 @@
= link_to explore_projects_filter_path(tag: tag.name) do
%i.fa.fa-tag
= tag.name
- = render 'explore/projects/dropdown'
diff --git a/app/views/explore/projects/_nav.html.haml b/app/views/explore/projects/_nav.html.haml
new file mode 100644
index 0000000000..614b543177
--- /dev/null
+++ b/app/views/explore/projects/_nav.html.haml
@@ -0,0 +1,10 @@
+%ul.nav-links
+ = nav_link(page: [trending_explore_projects_path, explore_root_path]) do
+ = link_to trending_explore_projects_path do
+ Trending
+ = nav_link(page: starred_explore_projects_path) do
+ = link_to starred_explore_projects_path do
+ Most stars
+ = nav_link(page: explore_projects_path) do
+ = link_to explore_projects_path do
+ All
diff --git a/app/views/explore/projects/_projects.html.haml b/app/views/explore/projects/_projects.html.haml
index 669079e952..999a933390 100644
--- a/app/views/explore/projects/_projects.html.haml
+++ b/app/views/explore/projects/_projects.html.haml
@@ -1,5 +1,5 @@
- if projects.any?
- .public-projects
+ .projects-list-holder
= render 'shared/projects/list', projects: projects
- else
.nothing-here-block
diff --git a/app/views/explore/projects/index.html.haml b/app/views/explore/projects/index.html.haml
index b9a958fbe7..dca7549857 100644
--- a/app/views/explore/projects/index.html.haml
+++ b/app/views/explore/projects/index.html.haml
@@ -6,7 +6,10 @@
- else
= render 'explore/head'
-.gray-content-block.clearfix.second-block
+.top-area
+ = render 'explore/projects/nav'
+
+.gray-content-block.second-block.clearfix
= render 'filter'
+
= render 'projects', projects: @projects
-= paginate @projects, theme: "gitlab"
diff --git a/app/views/explore/projects/starred.html.haml b/app/views/explore/projects/starred.html.haml
index 95d46e331f..ec46175510 100644
--- a/app/views/explore/projects/starred.html.haml
+++ b/app/views/explore/projects/starred.html.haml
@@ -6,12 +6,5 @@
- else
= render 'explore/head'
-.explore-trending-block
- .gray-content-block.second-block
- .pull-right
- = render 'explore/projects/dropdown'
- .oneline
- %i.fa.fa-star
- See most starred projects
- = render 'projects', projects: @starred_projects
- = paginate @starred_projects, theme: 'gitlab'
+= render 'explore/projects/nav'
+= render 'projects', projects: @projects
diff --git a/app/views/explore/projects/trending.html.haml b/app/views/explore/projects/trending.html.haml
index fa0b718e48..ec46175510 100644
--- a/app/views/explore/projects/trending.html.haml
+++ b/app/views/explore/projects/trending.html.haml
@@ -6,11 +6,5 @@
- else
= render 'explore/head'
-.explore-trending-block
- .gray-content-block.second-block
- .pull-right
- = render 'explore/projects/dropdown'
- .oneline
- %i.fa.fa-comments-o
- See most discussed projects for last month
- = render 'projects', projects: @trending_projects
+= render 'explore/projects/nav'
+= render 'projects', projects: @projects
diff --git a/app/views/groups/_projects.html.haml b/app/views/groups/_projects.html.haml
index bbafc08435..209729dc7e 100644
--- a/app/views/groups/_projects.html.haml
+++ b/app/views/groups/_projects.html.haml
@@ -1,11 +1,12 @@
-.projects-list-holder
- .projects-search-form
- .input-group
- = search_field_tag :filter_projects, nil, placeholder: 'Filter by name', class: 'projects-list-filter form-control', spellcheck: false
+.top-area
+ .nav-controls
+ = form_tag request.original_url, method: :get, class: 'project-filter-form', id: 'project-filter-form' do |f|
+ - 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
- %span.input-group-btn
- = link_to new_project_path(namespace_id: @group.id), class: 'btn btn-green' do
- %i.fa.fa-plus
- New Project
+ = 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
diff --git a/app/views/groups/edit.html.haml b/app/views/groups/edit.html.haml
index e2f97fd933..3430f56a9c 100644
--- a/app/views/groups/edit.html.haml
+++ b/app/views/groups/edit.html.haml
@@ -1,5 +1,4 @@
- header_title group_title(@group, "Settings", edit_group_path(@group))
-- @blank_container = true
.panel.panel-default.prepend-top-default
.panel-heading
diff --git a/app/views/groups/group_members/_group_member.html.haml b/app/views/groups/group_members/_group_member.html.haml
index a79a0fcdc8..60234be8f8 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/group_members/index.html.haml b/app/views/groups/group_members/index.html.haml
index 6a8acc42af..6b7fd5746d 100644
--- a/app/views/groups/group_members/index.html.haml
+++ b/app/views/groups/group_members/index.html.haml
@@ -1,6 +1,5 @@
- page_title "Members"
- header_title group_title(@group, "Members", group_group_members_path(@group))
-- @blank_container = true
.group-members-page.prepend-top-default
- if current_user && current_user.can?(:admin_group_member, @group)
@@ -20,7 +19,7 @@
group members
%small
(#{@members.total_count})
- .pull-right
+ .controls
= form_tag group_group_members_path(@group), method: :get, class: 'form-inline member-search-form' do
.form-group
= search_field_tag :search, params[:search], { placeholder: 'Find existing member by name', class: 'form-control', spellcheck: false }
diff --git a/app/views/groups/issues.html.haml b/app/views/groups/issues.html.haml
index 90ade1e168..b0805593fd 100644
--- a/app/views/groups/issues.html.haml
+++ b/app/views/groups/issues.html.haml
@@ -4,17 +4,15 @@
- if current_user
= auto_discovery_link_tag(:atom, issues_group_url(@group, format: :atom, private_token: current_user.private_token), title: "#{@group.name} issues")
-.project-issuable-filter
- .controls
- .pull-left
- - if current_user
- .hidden-xs.pull-left
- = link_to issues_group_url(@group, format: :atom, private_token: current_user.private_token), class: 'btn' do
- %i.fa.fa-rss
-
+.top-area
+ = render 'shared/issuable/nav', type: :issues
+ .nav-controls
+ - if current_user
+ = link_to issues_group_url(@group, format: :atom, private_token: current_user.private_token), class: 'btn' do
+ = icon('rss')
= render 'shared/new_project_item_select', path: 'issues/new', label: "New Issue"
- = render 'shared/issuable/filter', type: :issues
+= render 'shared/issuable/filter', type: :issues
.gray-content-block.second-block
Only issues from
diff --git a/app/views/groups/merge_requests.html.haml b/app/views/groups/merge_requests.html.haml
index f662f5a8c1..e1c9dd931e 100644
--- a/app/views/groups/merge_requests.html.haml
+++ b/app/views/groups/merge_requests.html.haml
@@ -1,11 +1,12 @@
- page_title "Merge Requests"
- header_title group_title(@group, "Merge Requests", merge_requests_group_path(@group))
-.project-issuable-filter
- .controls
+.top-area
+ = render 'shared/issuable/nav', type: :merge_requests
+ .nav-controls
= render 'shared/new_project_item_select', path: 'merge_requests/new', label: "New Merge Request"
- = render 'shared/issuable/filter', type: :merge_requests
+= render 'shared/issuable/filter', type: :merge_requests
.gray-content-block.second-block
Only merge requests from
diff --git a/app/views/groups/milestones/index.html.haml b/app/views/groups/milestones/index.html.haml
index b221d3a89a..ab307708b7 100644
--- a/app/views/groups/milestones/index.html.haml
+++ b/app/views/groups/milestones/index.html.haml
@@ -1,17 +1,15 @@
- page_title "Milestones"
- header_title group_title(@group, "Milestones", group_milestones_path(@group))
-.project-issuable-filter
- .controls
- - if can?(current_user, :admin_milestones, @group)
- .pull-right
- %span.pull-right.hidden-xs
- = link_to new_group_milestone_path(@group), class: "btn btn-new" do
- = icon('plus')
- New Milestone
-
+.top-area
= render 'shared/milestones_filter'
+ .nav-controls
+ - if can?(current_user, :admin_milestones, @group)
+ = link_to new_group_milestone_path(@group), class: "btn btn-new" do
+ = icon('plus')
+ New Milestone
+
.gray-content-block
Only milestones from
%strong #{@group.name}
diff --git a/app/views/groups/projects.html.haml b/app/views/groups/projects.html.haml
index 9ca11ed117..dd75766121 100644
--- a/app/views/groups/projects.html.haml
+++ b/app/views/groups/projects.html.haml
@@ -6,9 +6,9 @@
%strong= @group.name
projects:
- if can? current_user, :admin_group, @group
- .panel-head-actions
+ .controls
= link_to new_project_path(namespace_id: @group.id), class: "btn btn-sm btn-success" do
- %i.fa.fa-plus
+ = icon('plus')
New Project
%ul.well-list
- @projects.each do |project|
diff --git a/app/views/groups/show.atom.builder b/app/views/groups/show.atom.builder
index 5cc0f5e1d2..c66b82bb48 100644
--- a/app/views/groups/show.atom.builder
+++ b/app/views/groups/show.atom.builder
@@ -4,7 +4,7 @@ xml.feed "xmlns" => "http://www.w3.org/2005/Atom", "xmlns:media" => "http://sear
xml.link href: group_url(@group, format: :atom, private_token: current_user.try(:private_token)), rel: "self", type: "application/atom+xml"
xml.link href: group_url(@group), rel: "alternate", type: "text/html"
xml.id group_url(@group)
- xml.updated @events.latest_update_time.xmlschema if @events.any?
+ xml.updated @events[0].updated_at.xmlschema if @events[0]
@events.each do |event|
event_to_atom(xml, event)
diff --git a/app/views/groups/show.html.haml b/app/views/groups/show.html.haml
index ebb3df7dca..6148d8cb3d 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 }
@@ -47,7 +46,7 @@
= render 'shared/event_filter'
- .content_list
+ .content_list{data: {href: events_group_path}}
= spinner
.tab-pane#projects
diff --git a/app/views/help/_shortcuts.html.haml b/app/views/help/_shortcuts.html.haml
index 9ee6f07b26..8e982718d2 100644
--- a/app/views/help/_shortcuts.html.haml
+++ b/app/views/help/_shortcuts.html.haml
@@ -40,10 +40,6 @@
%td.shortcut
.key enter
%td Open Selection
- %tr
- %td.shortcut
- .key t
- %td Go to finding file
%tbody
%tr
%th
@@ -161,6 +157,10 @@
.key s
%td
Go to snippets
+ %tr
+ %td.shortcut
+ .key t
+ %td Go to finding file
.col-lg-4
%table.shortcut-mappings
%tbody{ class: 'hidden-shortcut network', style: 'display:none' }
diff --git a/app/views/help/ui.html.haml b/app/views/help/ui.html.haml
index 7b45bd0905..746386cab5 100644
--- a/app/views/help/ui.html.haml
+++ b/app/views/help/ui.html.haml
@@ -138,8 +138,32 @@
%h2#navs Navigation
+ %h4
+ %code .top-area
+ %p Holder for top page navigation. Includes navigation, search field, sorting and button
+
+ .example
+ .top-area
+ %ul.nav-links
+ %li.active
+ %a Open
+ %li
+ %a Closed
+ .nav-controls
+ = text_field_tag 'sample', nil, class: 'form-control'
+ .dropdown
+ %button.dropdown-toggle.btn{type: 'button', 'data-toggle' => 'dropdown'}
+ %span Sort by name
+ %b.caret
+ %ul.dropdown-menu
+ %li
+ %a Sort by date
+
+ = link_to 'New issue', '#', class: 'btn btn-new'
+
%h4
%code .nav-links
+ %p Only nav links without button and search
.example
%ul.nav-links
%li.active
diff --git a/app/views/kaminari/gitlab/_next_page.html.haml b/app/views/kaminari/gitlab/_next_page.html.haml
index 00c5f0b6f4..c805914fc3 100644
--- a/app/views/kaminari/gitlab/_next_page.html.haml
+++ b/app/views/kaminari/gitlab/_next_page.html.haml
@@ -5,5 +5,9 @@
-# num_pages: total number of pages
-# per_page: number of items to fetch per page
-# remote: data-remote
-%li.next
- = link_to_unless current_page.last?, raw(t 'views.pagination.next'), url, rel: 'next', remote: remote
+- if current_page.last?
+ %li{ class: "next disabled" }
+ %span= raw(t 'views.pagination.next')
+- else
+ %li{ class: "next" }
+ = link_to raw(t 'views.pagination.next'), url, rel: 'next', remote: remote
diff --git a/app/views/kaminari/gitlab/_paginator.html.haml b/app/views/kaminari/gitlab/_paginator.html.haml
index 2f64518692..a12c53bcfe 100644
--- a/app/views/kaminari/gitlab/_paginator.html.haml
+++ b/app/views/kaminari/gitlab/_paginator.html.haml
@@ -10,13 +10,13 @@
%ul.pagination.clearfix
- unless current_page.first?
= first_page_tag unless num_pages < 5 # As kaminari will always show the first 5 pages
- = prev_page_tag
+ = prev_page_tag
- each_page do |page|
- if page.left_outer? || page.right_outer? || page.inside_window?
= page_tag page
- elsif !page.was_truncated?
= gap_tag
+ = next_page_tag
- unless current_page.last?
- = next_page_tag
= last_page_tag unless num_pages < 5
diff --git a/app/views/kaminari/gitlab/_prev_page.html.haml b/app/views/kaminari/gitlab/_prev_page.html.haml
index f673abdb3a..afb20455e0 100644
--- a/app/views/kaminari/gitlab/_prev_page.html.haml
+++ b/app/views/kaminari/gitlab/_prev_page.html.haml
@@ -5,5 +5,9 @@
-# num_pages: total number of pages
-# per_page: number of items to fetch per page
-# remote: data-remote
-%li{class: "prev" }
- = link_to_unless current_page.first?, raw(t 'views.pagination.previous'), url, rel: 'prev', remote: remote
+- if current_page.first?
+ %li{ class: "prev disabled" }
+ %span= raw(t 'views.pagination.previous')
+- else
+ %li{ class: "prev" }
+ = link_to raw(t 'views.pagination.previous'), url, rel: 'prev', remote: remote
diff --git a/app/views/layouts/_page.html.haml b/app/views/layouts/_page.html.haml
index 2615998977..c799e9c588 100644
--- a/app/views/layouts/_page.html.haml
+++ b/app/views/layouts/_page.html.haml
@@ -1,9 +1,10 @@
-.page-with-sidebar{ class: page_sidebar_class }
+.page-with-sidebar{ class: "#{page_sidebar_class} #{page_gutter_class}" }
= render "layouts/broadcast"
.sidebar-wrapper.nicescroll{ class: nav_sidebar_class }
.header-logo
- = link_to root_path, class: 'home', title: 'Dashboard', id: 'js-shortcuts-home' do
+ %a#logo
= brand_header_logo
+ = 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 a44f5762a6..20042e21bf 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_placeholder, class: "search-input form-control", spellcheck: false
+ = search_field_tag "search", nil, placeholder: 'Search', class: "search-input form-control", spellcheck: false
= 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/ci/_page.html.haml b/app/views/layouts/ci/_page.html.haml
index 7e90af21b2..a13241bebe 100644
--- a/app/views/layouts/ci/_page.html.haml
+++ b/app/views/layouts/ci/_page.html.haml
@@ -2,8 +2,9 @@
= render "layouts/broadcast"
.sidebar-wrapper.nicescroll{ class: nav_sidebar_class }
.header-logo
- = link_to root_path, class: 'home', title: 'Dashboard', id: 'js-shortcuts-home' do
+ %a#logo
= brand_header_logo
+ = 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 fcb6b835a7..4781ff2350 100644
--- a/app/views/layouts/header/_default.html.haml
+++ b/app/views/layouts/header/_default.html.haml
@@ -21,6 +21,10 @@
%li
= link_to admin_root_path, title: 'Admin Area', data: {toggle: 'tooltip', placement: 'bottom', container: 'body'} do
= icon('wrench fw')
+ %li
+ = 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
@@ -39,4 +43,4 @@
= 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/nav/_admin.html.haml b/app/views/layouts/nav/_admin.html.haml
index cffdb52cc2..280a1b9372 100644
--- a/app/views/layouts/nav/_admin.html.haml
+++ b/app/views/layouts/nav/_admin.html.haml
@@ -1,6 +1,6 @@
%ul.nav.nav-sidebar
= nav_link(controller: :dashboard, html_options: {class: 'home'}) do
- = link_to admin_root_path, title: "Stats" do
+ = link_to admin_root_path, title: 'Overview' do
= icon('dashboard fw')
%span
Overview
@@ -25,13 +25,13 @@
%span
Deploy Keys
= nav_link path: ['runners#index', 'runners#show'] do
- = link_to admin_runners_path do
+ = link_to admin_runners_path, title: 'Runners' do
= icon('cog fw')
%span
Runners
%span.count= number_with_delimiter(Ci::Runner.count(:all))
= nav_link path: 'builds#index' do
- = link_to admin_builds_path do
+ = link_to admin_builds_path, title: 'Builds' do
= icon('link fw')
%span
Builds
@@ -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
@@ -82,6 +87,14 @@
Abuse Reports
%span.count= number_with_delimiter(AbuseReport.count(:all))
+ - if askimet_enabled?
+ = nav_link(controller: :spam_logs) do
+ = link_to admin_spam_logs_path, title: "Spam Logs" do
+ = icon('exclamation-triangle fw')
+ %span
+ Spam Logs
+ %span.count= number_with_delimiter(SpamLog.count(:all))
+
= nav_link(controller: :application_settings, html_options: { class: 'separate-item'}) do
= link_to admin_application_settings_path, title: 'Settings' do
= icon('cogs fw')
diff --git a/app/views/layouts/nav/_dashboard.html.haml b/app/views/layouts/nav/_dashboard.html.haml
index 106abd24a5..db0cf39392 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/layouts/nav/_project.html.haml b/app/views/layouts/nav/_project.html.haml
index 270ccfd387..319974e12c 100644
--- a/app/views/layouts/nav/_project.html.haml
+++ b/app/views/layouts/nav/_project.html.haml
@@ -98,6 +98,13 @@
%span
Wiki
+ - if project_nav_tab? :forks
+ = nav_link(controller: :forks, action: :index) do
+ = link_to namespace_project_forks_path(@project.namespace, @project), title: 'Forks' do
+ = icon('code-fork fw')
+ %span
+ Forks
+
- if project_nav_tab? :snippets
= nav_link(controller: :snippets) do
= link_to namespace_project_snippets_path(@project.namespace, @project), title: 'Snippets', class: 'shortcuts-snippets' do
diff --git a/app/views/notify/_note_message.html.haml b/app/views/notify/_note_message.html.haml
index 00cb4aa24c..12ded41fbf 100644
--- a/app/views/notify/_note_message.html.haml
+++ b/app/views/notify/_note_message.html.haml
@@ -1,2 +1,5 @@
+- if current_application_settings.email_author_in_body
+ %div
+ #{link_to @note.author_name, user_url(@note.author)} wrote:
%div
= markdown(@note.note, pipeline: :email)
diff --git a/app/views/notify/build_fail_email.html.haml b/app/views/notify/build_fail_email.html.haml
index f4e9749e5c..81d6503731 100644
--- a/app/views/notify/build_fail_email.html.haml
+++ b/app/views/notify/build_fail_email.html.haml
@@ -1,9 +1,10 @@
- content_for :header do
%h1{style: "background: #c40834; color: #FFF; font: normal 20px Helvetica, Arial, sans-serif; margin: 0; padding: 5px 10px; line-height: 32px; font-size: 16px;"}
GitLab (build failed)
+
%h3
Project:
- = link_to ci_project_url(@project) do
+ = link_to namespace_project_url(@project.namespace, @project) do
= @project.name
%p
diff --git a/app/views/notify/build_success_email.html.haml b/app/views/notify/build_success_email.html.haml
index 8b004d34cc..5d247eb4cf 100644
--- a/app/views/notify/build_success_email.html.haml
+++ b/app/views/notify/build_success_email.html.haml
@@ -4,7 +4,7 @@
%h3
Project:
- = link_to ci_project_url(@project) do
+ = link_to namespace_project_url(@project.namespace, @project) do
= @project.name
%p
diff --git a/app/views/notify/new_issue_email.html.haml b/app/views/notify/new_issue_email.html.haml
index d3b799fca2..ad3ab2525b 100644
--- a/app/views/notify/new_issue_email.html.haml
+++ b/app/views/notify/new_issue_email.html.haml
@@ -1,3 +1,6 @@
+- if current_application_settings.email_author_in_body
+ %div
+ #{link_to @issue.author_name, user_url(@issue.author)} wrote:
-if @issue.description
= markdown(@issue.description, pipeline: :email)
diff --git a/app/views/notify/new_merge_request_email.html.haml b/app/views/notify/new_merge_request_email.html.haml
index 90ebdfc3fe..23423e7d98 100644
--- a/app/views/notify/new_merge_request_email.html.haml
+++ b/app/views/notify/new_merge_request_email.html.haml
@@ -1,3 +1,6 @@
+- if current_application_settings.email_author_in_body
+ %div
+ #{link_to @merge_request.author_name, user_url(@merge_request.author)} wrote:
%p.details
!= merge_path_description(@merge_request, '→')
diff --git a/app/views/notify/repository_push_email.html.haml b/app/views/notify/repository_push_email.html.haml
index 3dd2595f1a..f2e405b14f 100644
--- a/app/views/notify/repository_push_email.html.haml
+++ b/app/views/notify/repository_push_email.html.haml
@@ -18,7 +18,7 @@
%div
%span by #{commit.author_name}
%i at #{commit.committed_date.to_s(:iso8601)}
- %pre.commit-message
+ %pre.commit-message
= commit.safe_message
%h4 #{pluralize @message.diffs_count, "changed file"}:
diff --git a/app/views/profiles/accounts/show.html.haml b/app/views/profiles/accounts/show.html.haml
index a42fd38de3..9fa96084f9 100644
--- a/app/views/profiles/accounts/show.html.haml
+++ b/app/views/profiles/accounts/show.html.haml
@@ -1,6 +1,5 @@
- page_title "Account"
- header_title page_title, profile_account_path
-- @blank_container = true
- if current_user.ldap_user?
.alert.alert-info
@@ -32,34 +31,33 @@
- else
= f.submit 'Generate', class: "btn btn-default"
- - unless current_user.ldap_user?
- .panel.panel-default
- .panel-heading
- Two-factor Authentication
- .panel-body
- - if current_user.two_factor_enabled?
- .pull-right
- = link_to 'Disable Two-factor Authentication', profile_two_factor_auth_path, method: :delete, class: 'btn btn-close btn-sm',
- data: { confirm: 'Are you sure?' }
- %p.text-success
- %strong
- Two-factor Authentication is enabled
- %p
- If you lose your recovery codes you can
- %strong
- = succeed ',' do
- = link_to 'generate new ones', codes_profile_two_factor_auth_path, method: :post, data: { confirm: 'Are you sure?' }
- invalidating all previous codes.
+ .panel.panel-default
+ .panel-heading
+ Two-factor Authentication
+ .panel-body
+ - if current_user.two_factor_enabled?
+ .pull-right
+ = link_to 'Disable Two-factor Authentication', profile_two_factor_auth_path, method: :delete, class: 'btn btn-close btn-sm',
+ data: { confirm: 'Are you sure?' }
+ %p.text-success
+ %strong
+ Two-factor Authentication is enabled
+ %p
+ If you lose your recovery codes you can
+ %strong
+ = succeed ',' do
+ = link_to 'generate new ones', codes_profile_two_factor_auth_path, method: :post, data: { confirm: 'Are you sure?' }
+ invalidating all previous codes.
- - else
- %p
- Increase your account's security by enabling two-factor authentication (2FA).
- %p
- Each time you log in you’ll be required to provide your username and
- password as usual, plus a randomly-generated code from your phone.
+ - else
+ %p
+ Increase your account's security by enabling two-factor authentication (2FA).
+ %p
+ Each time you log in you’ll be required to provide your username and
+ password as usual, plus a randomly-generated code from your phone.
- .form-actions
- = link_to 'Enable Two-factor Authentication', new_profile_two_factor_auth_path, class: 'btn btn-success'
+ .form-actions
+ = link_to 'Enable Two-factor Authentication', new_profile_two_factor_auth_path, class: 'btn btn-success'
- if button_based_providers.any?
.panel.panel-default
diff --git a/app/views/profiles/applications.html.haml b/app/views/profiles/applications.html.haml
index 0436c2213d..86f3582340 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 8fdba45b19..8f45f41cfe 100644
--- a/app/views/profiles/audit_log.html.haml
+++ b/app/views/profiles/audit_log.html.haml
@@ -1,7 +1,7 @@
- page_title "Audit Log"
- header_title page_title, audit_log_profile_path
-.gray-content-block.top-block
+.alert.alert-help.prepend-top-default
History of authentications
.prepend-top-default
diff --git a/app/views/profiles/emails/index.html.haml b/app/views/profiles/emails/index.html.haml
index 1d140347a5..705e180471 100644
--- a/app/views/profiles/emails/index.html.haml
+++ b/app/views/profiles/emails/index.html.haml
@@ -1,24 +1,22 @@
- 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.
+.alert.alert-help.prepend-top-default
+ %ul
+ %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
diff --git a/app/views/profiles/keys/index.html.haml b/app/views/profiles/keys/index.html.haml
index 17a4195030..c9a6a93f54 100644
--- a/app/views/profiles/keys/index.html.haml
+++ b/app/views/profiles/keys/index.html.haml
@@ -1,14 +1,14 @@
- page_title "SSH Keys"
- header_title page_title, profile_keys_path
-.gray-content-block.top-block
- .pull-right
+.top-area
+ .nav-text
+ Before you can add an SSH key you need to
+ = link_to "generate it.", help_page_path("ssh", "README")
+ .nav-controls
= 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'
diff --git a/app/views/profiles/notifications/show.html.haml b/app/views/profiles/notifications/show.html.haml
index 0bcadc965f..d5f61d9f0c 100644
--- a/app/views/profiles/notifications/show.html.haml
+++ b/app/views/profiles/notifications/show.html.haml
@@ -1,9 +1,6 @@
- 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|
-if @user.errors.any?
diff --git a/app/views/profiles/passwords/edit.html.haml b/app/views/profiles/passwords/edit.html.haml
index fab7c45c9b..ab070c09be 100644
--- a/app/views/profiles/passwords/edit.html.haml
+++ b/app/views/profiles/passwords/edit.html.haml
@@ -1,7 +1,7 @@
- page_title "Password"
- header_title page_title, edit_profile_password_path
-.gray-content-block.top-block
+.alert.alert-help.prepend-top-default
- if @user.password_automatically_set?
Set your password.
- else
diff --git a/app/views/profiles/preferences/show.html.haml b/app/views/profiles/preferences/show.html.haml
index 877589dc39..1a53b4393e 100644
--- a/app/views/profiles/preferences/show.html.haml
+++ b/app/views/profiles/preferences/show.html.haml
@@ -1,8 +1,7 @@
- page_title 'Preferences'
- header_title page_title, profile_preferences_path
-- @blank_container = true
-.alert.alert-help
+.alert.alert-help.prepend-top-default
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.
diff --git a/app/views/profiles/show.html.haml b/app/views/profiles/show.html.haml
index add9a00138..5051c6bf83 100644
--- a/app/views/profiles/show.html.haml
+++ b/app/views/profiles/show.html.haml
@@ -1,4 +1,4 @@
-.gray-content-block.top-block
+.alert.alert-help.prepend-top-default
This information will appear on your profile.
- if current_user.ldap_user?
Some options are unavailable for LDAP accounts
diff --git a/app/views/profiles/two_factor_auths/new.html.haml b/app/views/profiles/two_factor_auths/new.html.haml
index 1a5b6efce3..b2830aa083 100644
--- a/app/views/profiles/two_factor_auths/new.html.haml
+++ b/app/views/profiles/two_factor_auths/new.html.haml
@@ -1,6 +1,6 @@
- page_title 'Two-factor Authentication', 'Account'
-%h2.page-title Two-Factor Authentication (2FA)
+%h2.page-title Two-factor Authentication (2FA)
%p
Download the Google Authenticator application from App Store for iOS or Google
Play for Android and scan this code.
diff --git a/app/views/projects/_home_panel.html.haml b/app/views/projects/_home_panel.html.haml
index 298c666499..b45df44f27 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/blob/_blob.html.haml b/app/views/projects/blob/_blob.html.haml
index 3d8d88834e..f3bfe0a18b 100644
--- a/app/views/projects/blob/_blob.html.haml
+++ b/app/views/projects/blob/_blob.html.haml
@@ -35,7 +35,10 @@
- if blob.lfs_pointer?
= render "download", blob: blob
- elsif blob.text?
- = render "text", blob: blob
+ - if blob_svg?(blob)
+ = render "image", blob: blob
+ - else
+ = render "text", blob: blob
- elsif blob.image?
= render "image", blob: blob
- else
diff --git a/app/views/projects/blob/_image.html.haml b/app/views/projects/blob/_image.html.haml
index c090f690d1..113dba5d83 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: "data:#{blob.mime_type};base64,#{Base64.encode64(blob.data)}"}
+ - if blob_svg?(blob)
+ - # 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, @id)}
diff --git a/app/views/projects/blob/_text.html.haml b/app/views/projects/blob/_text.html.haml
index 906e5ccb36..d09cd73558 100644
--- a/app/views/projects/blob/_text.html.haml
+++ b/app/views/projects/blob/_text.html.haml
@@ -1,3 +1,4 @@
+- blob.load_all_data!(@repository)
- if markup?(blob.name)
.file-content.wiki
= render_markup(blob.name, blob.data)
diff --git a/app/views/projects/branches/index.html.haml b/app/views/projects/branches/index.html.haml
index 204def6079..7afea5a504 100644
--- a/app/views/projects/branches/index.html.haml
+++ b/app/views/projects/branches/index.html.haml
@@ -10,7 +10,7 @@
.dropdown.inline
%button.dropdown-toggle.btn{type: 'button', 'data-toggle' => 'dropdown'}
- %span.light sort:
+ %span.light
- if @sort.present?
= @sort.humanize
- else
diff --git a/app/views/projects/builds/index.html.haml b/app/views/projects/builds/index.html.haml
index 5d18c0d803..5e3bd14565 100644
--- a/app/views/projects/builds/index.html.haml
+++ b/app/views/projects/builds/index.html.haml
@@ -1,13 +1,7 @@
- page_title "Builds"
= render "header_title"
-.project-issuable-filter
- .controls
- - if can?(current_user, :manage_builds, @project)
- .pull-left.hidden-xs
- - if @all_builds.running_or_pending.any?
- = link_to 'Cancel running', cancel_all_namespace_project_builds_path(@project.namespace, @project), data: { confirm: 'Are you sure?' }, class: 'btn btn-danger', method: :post
-
+.top-area
%ul.nav-links
%li{class: ('active' if @scope.nil?)}
= link_to project_builds_path(@project) do
@@ -27,6 +21,16 @@
%span.badge.js-running-count
= number_with_delimiter(@all_builds.finished.count(:id))
+ .nav-controls
+ - if can?(current_user, :update_build, @project)
+ - if @all_builds.running_or_pending.any?
+ = link_to 'Cancel running', cancel_all_namespace_project_builds_path(@project.namespace, @project),
+ data: { confirm: 'Are you sure?' }, class: 'btn btn-danger', method: :post
+
+ = link_to ci_lint_path, class: 'btn btn-default' do
+ = icon('wrench')
+ %span CI Lint
+
.gray-content-block
#{(@scope || 'running').capitalize} builds from this project
diff --git a/app/views/projects/builds/show.html.haml b/app/views/projects/builds/show.html.haml
index ba1fdc6f0e..8eec78a557 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
@@ -89,43 +95,60 @@
Test coverage
%h1 #{@build.coverage}%
- - if current_user && can?(current_user, :read_build_artifacts, @project) && @build.artifacts?
-
+ - if can?(current_user, :read_build, @project) && @build.artifacts?
.build-widget.artifacts
%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 current_user && can?(current_user, :manage_builds, @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 can?(current_user, :update_build, @project)
+ .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.duration
+ - 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/buttons/_dropdown.html.haml b/app/views/projects/buttons/_dropdown.html.haml
index 511863d774..e7c85edff9 100644
--- a/app/views/projects/buttons/_dropdown.html.haml
+++ b/app/views/projects/buttons/_dropdown.html.haml
@@ -46,7 +46,7 @@
- continue_params = { to: namespace_project_new_blob_path(@project.namespace, @project, @project.default_branch || 'master'),
notice: edit_in_new_fork_notice,
notice_now: edit_in_new_fork_notice_now }
- - fork_path = namespace_project_fork_path(@project.namespace, @project, namespace_key: current_user.namespace.id,
+ - fork_path = namespace_project_forks_path(@project.namespace, @project, namespace_key: current_user.namespace.id,
continue: continue_params)
= link_to fork_path, method: :post do
= icon('file fw')
diff --git a/app/views/projects/commit/_builds.html.haml b/app/views/projects/commit/_builds.html.haml
index 329aaa0bb8..befad27666 100644
--- a/app/views/projects/commit/_builds.html.haml
+++ b/app/views/projects/commit/_builds.html.haml
@@ -1,6 +1,6 @@
.gray-content-block.middle-block
.pull-right
- - if can?(current_user, :manage_builds, @ci_commit.project)
+ - if can?(current_user, :update_build, @ci_commit.project)
- if @ci_commit.builds.latest.failed.any?(&:retryable?)
= link_to "Retry failed", retry_builds_namespace_project_commit_path(@ci_commit.project.namespace, @ci_commit.project, @ci_commit.sha), class: 'btn btn-grouped btn-primary', method: :post
diff --git a/app/views/projects/commit/_commit_box.html.haml b/app/views/projects/commit/_commit_box.html.haml
index bbe820b884..71995fcc48 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 0000000000..52ca3ed5b1
--- /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 new merge request 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 05dbe5ebea..21e186120c 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/commit_statuses/_commit_status.html.haml b/app/views/projects/commit_statuses/_commit_status.html.haml
index 2e3c956ddc..a3449d1ae0 100644
--- a/app/views/projects/commit_statuses/_commit_status.html.haml
+++ b/app/views/projects/commit_statuses/_commit_status.html.haml
@@ -1,6 +1,6 @@
%tr.commit_status
%td.status
- - if commit_status.target_url
+ - if can?(current_user, :read_commit_status, commit_status) && commit_status.target_url
= link_to commit_status.target_url, class: "ci-status ci-#{commit_status.status}" do
= ci_icon_for_status(commit_status.status)
= commit_status.status
@@ -8,14 +8,14 @@
= ci_status_with_icon(commit_status.status)
%td.commit_status-link
- - if commit_status.target_url
+ - if can?(current_user, :read_commit_status, commit_status) && commit_status.target_url
= link_to commit_status.target_url do
%strong ##{commit_status.id}
- else
%strong ##{commit_status.id}
- if commit_status.show_warning?
- %i.fa.fa-warning.text-warning
+ %i.fa.fa-warning.text-warning{data: { toggle: "tooltip" }, title: "This build is stuck, open it to know more"}
- if defined?(commit_sha) && commit_sha
%td
@@ -66,10 +66,10 @@
%td
.pull-right
- - if current_user && can?(current_user, :read_build_artifacts, commit_status.project) && commit_status.artifacts_download_url
+ - if can?(current_user, :read_commit_status, commit_status) && commit_status.artifacts_download_url
= link_to commit_status.artifacts_download_url, title: 'Download artifacts' do
%i.fa.fa-download
- - if current_user && can?(current_user, :manage_builds, commit_status.project)
+ - if can?(current_user, :update_commit_status, commit_status)
- if commit_status.active?
- if commit_status.cancel_url
= link_to commit_status.cancel_url, method: :post, title: 'Cancel' do
diff --git a/app/views/projects/commits/show.html.haml b/app/views/projects/commits/show.html.haml
index ede64d47ab..c52cf25d40 100644
--- a/app/views/projects/commits/show.html.haml
+++ b/app/views/projects/commits/show.html.haml
@@ -11,7 +11,10 @@
= render 'shared/ref_switcher', destination: 'commits'
.block-controls.hidden-xs.hidden-sm
- - if create_mr_button?(@repository.root_ref, @ref)
+ - if @merge_request.present?
+ .control
+ = link_to "View Open Merge Request", namespace_project_merge_request_path(@project.namespace, @project, @merge_request), class: 'btn'
+ - elsif create_mr_button?(@repository.root_ref, @ref)
.control
= link_to create_mr_path(@repository.root_ref, @ref), class: 'btn btn-success' do
= icon('plus')
diff --git a/app/views/projects/compare/_form.html.haml b/app/views/projects/compare/_form.html.haml
index efc25eda26..4ab81f3635 100644
--- a/app/views/projects/compare/_form.html.haml
+++ b/app/views/projects/compare/_form.html.haml
@@ -13,12 +13,13 @@
= text_field_tag :to, params[:to], class: "form-control", required: true
= button_tag "Compare", class: "btn btn-create commits-compare-btn"
- - if create_mr_button?
+ - if @merge_request.present?
+ = link_to "View Open Merge Request", namespace_project_merge_request_path(@project.namespace, @project, @merge_request), class: 'prepend-left-10 btn'
+ - elsif create_mr_button?
= link_to create_mr_path, class: 'prepend-left-10 btn' do
= icon("plus")
Create Merge Request
-
:javascript
var availableTags = #{@project.repository.ref_names.to_json};
diff --git a/app/views/projects/diffs/_file.html.haml b/app/views/projects/diffs/_file.html.haml
index fc0eaef228..3ac058a3bf 100644
--- a/app/views/projects/diffs/_file.html.haml
+++ b/app/views/projects/diffs/_file.html.haml
@@ -7,16 +7,20 @@
= submodule_link(blob, @commit.id, project.repository)
- else
= blob_icon blob.mode, blob.name
- = link_to "#diff-#{i}" do
- %strong
- = diff_file.new_path
- - if diff_file.deleted_file
- deleted
- - elsif diff_file.renamed_file
- renamed from
- %strong
- = diff_file.old_path
+ = link_to "#diff-#{i}" do
+ - if diff_file.renamed_file
+ - old_path, new_path = mark_inline_diffs(diff_file.old_path, diff_file.new_path)
+ %strong.filename.old
+ = old_path
+ →
+ %strong.filename.new
+ = new_path
+ - else
+ %strong
+ = diff_file.new_path
+ - if diff_file.deleted_file
+ deleted
- if diff_file.mode_changed?
%small
diff --git a/app/views/projects/diffs/_image.html.haml b/app/views/projects/diffs/_image.html.haml
index 058b71b21f..752e92e2e6 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 5e835b10e1..d75e9ef2a4 100644
--- a/app/views/projects/diffs/_text_file.html.haml
+++ b/app/views/projects/diffs/_text_file.html.haml
@@ -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/edit.html.haml b/app/views/projects/edit.html.haml
index 8a99aceef7..f2e56081af 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,17 +117,18 @@
.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
%span.descr Faster
+
.form-group
= f.label :build_timeout_in_minutes, 'Timeout', class: 'control-label'
.col-sm-10
@@ -158,6 +157,13 @@
phpunit --coverage-text --colors=never (PHP) -
%code ^\s*Lines:\s*\d+.\d+\%
+ .form-group
+ .col-sm-offset-2.col-sm-10
+ .checkbox
+ = f.label :public_builds do
+ = f.check_box :public_builds
+ %strong Public builds
+ .help-block Allow everyone to access builds for Public and Internal projects
%fieldset.features
%legend
diff --git a/app/views/projects/forks/index.html.haml b/app/views/projects/forks/index.html.haml
new file mode 100644
index 0000000000..ace22625d1
--- /dev/null
+++ b/app/views/projects/forks/index.html.haml
@@ -0,0 +1,55 @@
+.top-area
+ .nav-text
+ - 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' }
+
+ .dropdown
+ %button.dropdown-toggle.btn.sort-forks{type: 'button', 'data-toggle' => 'dropdown'}
+ %span.light sort:
+ - if @sort.present?
+ = sort_options_hash[@sort]
+ - else
+ = sort_title_recently_created
+ %b.caret
+ %ul.dropdown-menu.dropdown-menu-align-right
+ %li
+ - excluded_filters = [:state, :scope, :label_name, :milestone_id, :assignee_id, :author_id]
+ = link_to page_filter_path(sort: sort_value_recently_created, without: excluded_filters) do
+ = sort_title_recently_created
+ = link_to page_filter_path(sort: sort_value_oldest_created, without: excluded_filters) do
+ = sort_title_oldest_created
+ = link_to page_filter_path(sort: sort_value_recently_updated, without: excluded_filters) do
+ = sort_title_recently_updated
+ = link_to page_filter_path(sort: sort_value_oldest_updated, without: excluded_filters) do
+ = sort_title_oldest_updated
+
+ - if current_user && can?(current_user, :fork_project, @project)
+ - if current_user.already_forked?(@project) && current_user.manageable_namespaces.size < 2
+ = link_to namespace_project_path(current_user, current_user.fork_of(@project)), title: 'Go to your fork', class: 'btn btn-new' do
+ = icon('code-fork fw')
+ Fork
+ - else
+ = link_to new_namespace_project_fork_path(@project.namespace, @project), title: "Fork project", class: 'btn btn-new' do
+ = icon('code-fork fw')
+ Fork
+
+
+.projects-list-holder
+ - if @forks.blank?
+ %ul.content-list
+ %li
+ .nothing-here-block No forks to show
+ - else
+ = render 'shared/projects/list', projects: @forks, use_creator_avatar: true,
+ forks: true, show_last_commit_as_description: true
+
+ - if @private_forks_count > 0
+ %ul.projects-list.private-forks-notice
+ %li.project-row
+ = 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/forks/new.html.haml b/app/views/projects/forks/new.html.haml
index 8a2c027a45..edabc2d3b4 100644
--- a/app/views/projects/forks/new.html.haml
+++ b/app/views/projects/forks/new.html.haml
@@ -22,7 +22,7 @@
- else
.fork-thumbnail
- = link_to namespace_project_fork_path(@project.namespace, @project, namespace_key: namespace.id), title: "Fork here", method: "POST", class: 'has_tooltip' do
+ = link_to namespace_project_forks_path(@project.namespace, @project, namespace_key: namespace.id), title: "Fork here", method: "POST", class: 'has_tooltip' do
= image_tag namespace_icon(namespace, 100)
.caption
%strong
diff --git a/app/views/projects/issues/_issue.html.haml b/app/views/projects/issues/_issue.html.haml
index f9cf4910df..654d8cd5ed 100644
--- a/app/views/projects/issues/_issue.html.haml
+++ b/app/views/projects/issues/_issue.html.haml
@@ -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/_issues.html.haml b/app/views/projects/issues/_issues.html.haml
index e0e89b764d..f34f3c0573 100644
--- a/app/views/projects/issues/_issues.html.haml
+++ b/app/views/projects/issues/_issues.html.haml
@@ -5,9 +5,4 @@
.nothing-here-block No issues to show
- if @issues.present?
- .issuable-filter-count
- %span.pull-right
- = number_with_delimiter(@issues.total_count)
- issues for this filter
-
= paginate @issues, theme: "gitlab"
diff --git a/app/views/projects/issues/index.html.haml b/app/views/projects/issues/index.html.haml
index d6260ab290..fde9304c0f 100644
--- a/app/views/projects/issues/index.html.haml
+++ b/app/views/projects/issues/index.html.haml
@@ -5,22 +5,19 @@
- if current_user
= auto_discovery_link_tag(:atom, namespace_project_issues_url(@project.namespace, @project, :atom, private_token: current_user.private_token), title: "#{@project.name} issues")
-.project-issuable-filter
- .controls
- .pull-left
- - if current_user
- .hidden-xs.pull-left
- = link_to namespace_project_issues_path(@project.namespace, @project, :atom, { private_token: current_user.private_token }), class: 'btn append-right-10' do
- %i.fa.fa-rss
-
+.top-area
+ = render 'shared/issuable/nav', type: :issues
+ .nav-controls
+ - if current_user
+ = link_to namespace_project_issues_path(@project.namespace, @project, :atom, { private_token: current_user.private_token }), class: 'btn append-right-10' do
+ = icon('rss')
= render 'shared/issuable/search_form', path: namespace_project_issues_path(@project.namespace, @project)
-
- if can? current_user, :create_issue, @project
- = link_to new_namespace_project_issue_path(@project.namespace, @project, issue: { assignee_id: @issuable_finder.assignee.try(:id), milestone_id: @issuable_finder.milestones.try(:first).try(:id) }), class: "btn btn-new pull-left", title: "New Issue", id: "new_issue_link" do
- %i.fa.fa-plus
+ = link_to new_namespace_project_issue_path(@project.namespace, @project, issue: { assignee_id: @issuable_finder.assignee.try(:id), milestone_id: @issuable_finder.milestones.try(:first).try(:id) }), class: "btn btn-new", title: "New Issue", id: "new_issue_link" do
+ = icon('plus')
New Issue
- = render 'shared/issuable/filter', type: :issues
+= render 'shared/issuable/filter', type: :issues
.issues-holder
= render "issues"
diff --git a/app/views/projects/issues/show.html.haml b/app/views/projects/issues/show.html.haml
index 7ed898ce72..69a0e2a0c4 100644
--- a/app/views/projects/issues/show.html.haml
+++ b/app/views/projects/issues/show.html.haml
@@ -6,21 +6,6 @@
.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
- ·
- opened by #{link_to_member(@project, @issue.author, size: 24)}
- ·
- = time_ago_with_tooltip(@issue.created_at, placement: 'bottom', html_class: 'issue_created_ago')
- - if @issue.updated_at != @issue.created_at
- %span
- ·
- = icon('edit', title: 'edited')
- = time_ago_with_tooltip(@issue.updated_at, placement: 'bottom', html_class: 'issue_edited_ago')
-
.pull-right
- 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
@@ -34,8 +19,21 @@
= icon('pencil-square-o')
Edit
+ .pull-left
+ .status-box{ class: "status-box-closed #{issue_button_visibility(@issue, false)}"} Closed
+ .status-box{ class: "status-box-open #{issue_button_visibility(@issue, true)}"} Open
+
+ .issue-meta
+ %span.identifier
+ Issue ##{@issue.iid}
+ %span.creator
+ ·
+ by #{link_to_member(@project, @issue.author, size: 24)}
+ ·
+ = time_ago_with_tooltip(@issue.created_at, placement: 'bottom', html_class: 'issue_created_ago')
+
.issue-details.issuable-details
- .detail-page-description.gray-content-block.second-block
+ .detail-page-description.content-block
%h2.title
= markdown escape_once(@issue.title), pipeline: :single_line
%div
@@ -46,19 +44,20 @@
= markdown(@issue.description, cache_key: [@issue, "description"])
%textarea.hidden.js-task-list-field
= @issue.description
+ - if @issue.updated_at != @issue.created_at
+ %small
+ Edited
+ = time_ago_with_tooltip(@issue.updated_at, placement: 'bottom', html_class: 'issue_edited_ago')
- .merge-requests
- = render 'merge_requests'
+ .merge-requests
+ = render 'merge_requests'
- .gray-content-block.second-block.oneline-block
+ .content-block
= render 'votes/votes_block', votable: @issue
.row
- %section.col-md-9
+ %section.col-md-12
.issuable-discussion
= render 'projects/issues/discussion'
- %aside.col-md-3
- = render 'shared/issuable/sidebar', issuable: @issue
-
- = render 'shared/show_aside'
+= render 'shared/issuable/sidebar', issuable: @issue
diff --git a/app/views/projects/issues/update.js.haml b/app/views/projects/issues/update.js.haml
index 2f0f3fcfb0..986d8c220d 100644
--- a/app/views/projects/issues/update.js.haml
+++ b/app/views/projects/issues/update.js.haml
@@ -1,3 +1,3 @@
-$('.issuable-sidebar').html("#{escape_javascript(render 'shared/issuable/sidebar', issuable: @issue)}");
-$('.issuable-sidebar').parent().effect('highlight')
-new Issue();
+$('aside.right-sidebar')[0].outerHTML = "#{escape_javascript(render 'shared/issuable/sidebar', issuable: @issue)}";
+$('aside.right-sidebar').effect('highlight');
+new IssuableContext();
diff --git a/app/views/projects/labels/_form.html.haml b/app/views/projects/labels/_form.html.haml
index 5ce2a7b985..d63d3a3ec2 100644
--- a/app/views/projects/labels/_form.html.haml
+++ b/app/views/projects/labels/_form.html.haml
@@ -11,6 +11,10 @@
= f.label :title, class: 'control-label'
.col-sm-10
= f.text_field :title, class: "form-control js-quick-submit", 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 b70a9fc9fe..5b35acc66c 100644
--- a/app/views/projects/labels/_label.html.haml
+++ b/app/views/projects/labels/_label.html.haml
@@ -1,5 +1,6 @@
%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) do
diff --git a/app/views/projects/labels/index.html.haml b/app/views/projects/labels/index.html.haml
index 9081bcfe9b..cc41130a9d 100644
--- a/app/views/projects/labels/index.html.haml
+++ b/app/views/projects/labels/index.html.haml
@@ -1,13 +1,14 @@
- page_title "Labels"
= render "header_title"
-.gray-content-block.top-block
- - if can? current_user, :admin_label, @project
- = link_to new_namespace_project_label_path(@project.namespace, @project), class: "pull-right btn btn-new" do
- = icon('plus')
- New label
- .oneline
+.top-area
+ .nav-text
Labels can be applied to issues and merge requests.
+ .nav-controls
+ - if can? current_user, :admin_label, @project
+ = link_to new_namespace_project_label_path(@project.namespace, @project), class: "btn btn-new" do
+ = icon('plus')
+ New label
.labels
- if @labels.present?
diff --git a/app/views/projects/merge_requests/_merge_request.html.haml b/app/views/projects/merge_requests/_merge_request.html.haml
index a051729dc3..b55f6a2d32 100644
--- a/app/views/projects/merge_requests/_merge_request.html.haml
+++ b/app/views/projects/merge_requests/_merge_request.html.haml
@@ -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
@@ -53,7 +64,7 @@
- if merge_request.labels.any?
- merge_request.labels.each do |label|
- = link_to_label(label, project: merge_request.project)
+ = link_to_label(label, project: merge_request.project, type: 'merge_request')
- if merge_request.tasks?
%span.task-status
diff --git a/app/views/projects/merge_requests/_merge_requests.html.haml b/app/views/projects/merge_requests/_merge_requests.html.haml
index 29d09d0a65..5473fa1916 100644
--- a/app/views/projects/merge_requests/_merge_requests.html.haml
+++ b/app/views/projects/merge_requests/_merge_requests.html.haml
@@ -5,10 +5,5 @@
.nothing-here-block No merge requests to show
- if @merge_requests.present?
- .issuable-filter-count
- %span.pull-right
- = number_with_delimiter(@merge_requests.total_count)
- merge requests for this filter
-
= paginate @merge_requests, theme: "gitlab"
diff --git a/app/views/projects/merge_requests/_show.html.haml b/app/views/projects/merge_requests/_show.html.haml
index 200bfa5ac4..648512e537 100644
--- a/app/views/projects/merge_requests/_show.html.haml
+++ b/app/views/projects/merge_requests/_show.html.haml
@@ -66,16 +66,13 @@
.tab-content
#notes.notes.tab-pane.voting_notes
- .gray-content-block.second-block.oneline-block
+ .content-block.oneline-block
= render 'votes/votes_block', votable: @merge_request
.row
- %section.col-md-9
+ %section.col-md-12
.issuable-discussion
= render "projects/merge_requests/discussion"
- %aside.col-md-3
- = render 'shared/issuable/sidebar', issuable: @merge_request
- = render 'shared/show_aside'
#commits.commits.tab-pane
- # This tab is always loaded via AJAX
@@ -87,6 +84,10 @@
.mr-loading-status
= 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/index.html.haml b/app/views/projects/merge_requests/index.html.haml
index 8d5d0394a8..e56a44e0a7 100644
--- a/app/views/projects/merge_requests/index.html.haml
+++ b/app/views/projects/merge_requests/index.html.haml
@@ -2,16 +2,19 @@
= render "header_title"
= render 'projects/last_push'
-.project-issuable-filter
- .controls
+
+.top-area
+ = render 'shared/issuable/nav', type: :merge_requests
+ .nav-controls
= render 'shared/issuable/search_form', path: namespace_project_merge_requests_path(@project.namespace, @project)
- merge_project = can?(current_user, :create_merge_request, @project) ? @project : (current_user && current_user.fork_of(@project))
- if merge_project
- .pull-left.hidden-xs
- = link_to new_namespace_project_merge_request_path(merge_project.namespace, merge_project), class: "btn btn-new", title: "New Merge Request" do
- %i.fa.fa-plus
- New Merge Request
- = render 'shared/issuable/filter', type: :merge_requests
+ = link_to new_namespace_project_merge_request_path(merge_project.namespace, merge_project), class: "btn btn-new", title: "New Merge Request" do
+ = icon('plus')
+ New Merge Request
+
+= render 'shared/issuable/filter', type: :merge_requests
+
.merge-requests-holder
= render 'merge_requests'
diff --git a/app/views/projects/merge_requests/show/_commits.html.haml b/app/views/projects/merge_requests/show/_commits.html.haml
index 7f904ec42a..a8f09f855d 100644
--- a/app/views/projects/merge_requests/show/_commits.html.haml
+++ b/app/views/projects/merge_requests/show/_commits.html.haml
@@ -1,4 +1,4 @@
-.gray-content-block.middle-block.oneline-block
+.content-block.oneline-block
= icon("sort-amount-desc")
Most recent commits displayed first
diff --git a/app/views/projects/merge_requests/show/_how_to_merge.html.haml b/app/views/projects/merge_requests/show/_how_to_merge.html.haml
index 877cc3d744..0dbd159298 100644
--- a/app/views/projects/merge_requests/show/_how_to_merge.html.haml
+++ b/app/views/projects/merge_requests/show/_how_to_merge.html.haml
@@ -45,6 +45,10 @@
- unless @merge_request.can_be_merged_by?(current_user)
%p
Note that pushing to GitLab requires write access to this repository.
+ %p
+ %strong Tip:
+ You can also checkout merge requests locally by
+ %a{href: 'https://gitlab.com/gitlab-org/gitlab-ce/blob/master/doc/workflow/merge_requests.md#checkout-merge-requests-locally', target: '_blank'} following these guidelines
:javascript
$(function(){
diff --git a/app/views/projects/merge_requests/show/_mr_box.html.haml b/app/views/projects/merge_requests/show/_mr_box.html.haml
index 0f81e5e891..602f787e6c 100644
--- a/app/views/projects/merge_requests/show/_mr_box.html.haml
+++ b/app/views/projects/merge_requests/show/_mr_box.html.haml
@@ -1,4 +1,4 @@
-.detail-page-description.gray-content-block.second-block
+.detail-page-description.content-block
%h2.title
= markdown escape_once(@merge_request.title), pipeline: :single_line
@@ -10,3 +10,8 @@
= markdown(@merge_request.description, cache_key: [@merge_request, "description"])
%textarea.hidden.js-task-list-field
= @merge_request.description
+
+ - if @merge_request.updated_at != @merge_request.created_at
+ %small
+ Edited
+ = time_ago_with_tooltip(@merge_request.updated_at, placement: 'bottom')
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 fc6fb2a0d4..14ea7b1778 100644
--- a/app/views/projects/merge_requests/show/_mr_title.html.haml
+++ b/app/views/projects/merge_requests/show/_mr_title.html.haml
@@ -5,14 +5,9 @@
Merge Request ##{@merge_request.iid}
%span.creator
·
- opened by #{link_to_member(@project, @merge_request.author, size: 24)}
+ by #{link_to_member(@project, @merge_request.author, size: 24)}
·
= time_ago_with_tooltip(@merge_request.created_at)
- - if @merge_request.updated_at != @merge_request.created_at
- %span
- ·
- = icon('edit', title: 'edited')
- = time_ago_with_tooltip(@merge_request.updated_at, placement: 'bottom')
.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 93db65ddf7..9cce5660e1 100644
--- a/app/views/projects/merge_requests/update.js.haml
+++ b/app/views/projects/merge_requests/update.js.haml
@@ -1,3 +1,3 @@
-$('.issuable-sidebar').html("#{escape_javascript(render 'shared/issuable/sidebar', issuable: @merge_request)}");
-$('.issuable-sidebar').parent().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 d1d602eecd..3abae9f0bf 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 0000000000..85a3a6ba9e
--- /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/milestones/_issue.html.haml b/app/views/projects/milestones/_issue.html.haml
index 133d802aac..ca51b8c745 100644
--- a/app/views/projects/milestones/_issue.html.haml
+++ b/app/views/projects/milestones/_issue.html.haml
@@ -1,9 +1,10 @@
%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
-
+ .issue-detail
+ = link_to [@project.namespace.becomes(Namespace), @project, issue] do
+ %span.issue-number ##{issue.iid}
+ - issue.labels.each do |label|
+ = render_colored_label(label)
+ - if issue.assignee
+ = image_tag avatar_icon(issue.assignee, 16), class: "avatar s24", alt: ''
diff --git a/app/views/projects/milestones/_issues.html.haml b/app/views/projects/milestones/_issues.html.haml
index 6e4df75a3d..6f8a341e47 100644
--- a/app/views/projects/milestones/_issues.html.haml
+++ b/app/views/projects/milestones/_issues.html.haml
@@ -1,6 +1,7 @@
.panel.panel-default
- .panel-heading= title
+ .panel-heading
+ = title
+ .pull-right= issues.size
%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_requests.html.haml b/app/views/projects/milestones/_merge_requests.html.haml
index 00889a5eb2..9a5a02af21 100644
--- a/app/views/projects/milestones/_merge_requests.html.haml
+++ b/app/views/projects/milestones/_merge_requests.html.haml
@@ -3,4 +3,3 @@
%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/index.html.haml b/app/views/projects/milestones/index.html.haml
index 114b06457a..abe567af1d 100644
--- a/app/views/projects/milestones/index.html.haml
+++ b/app/views/projects/milestones/index.html.haml
@@ -2,17 +2,14 @@
= render "header_title"
-.project-issuable-filter
- .controls
- - if can?(current_user, :admin_milestone, @project)
- = link_to new_namespace_project_milestone_path(@project.namespace, @project), class: "pull-right btn btn-new", title: "New Milestone" do
- %i.fa.fa-plus
- New Milestone
-
+.top-area
= render 'shared/milestones_filter'
-.gray-content-block
- Milestone allows you to group issues and set due date for it
+ .nav-controls
+ - if can?(current_user, :admin_milestone, @project)
+ = link_to new_namespace_project_milestone_path(@project.namespace, @project), class: "btn btn-new", title: "New Milestone" do
+ = icon('plus')
+ New Milestone
.milestones
%ul.content-list
diff --git a/app/views/projects/milestones/show.html.haml b/app/views/projects/milestones/show.html.haml
index 1142c58459..2cae1ac4e2 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.gray-content-block.second-block
+.detail-page-description.milestone-detail.second-block
%h2.title
= markdown escape_once(@milestone.title), pipeline: :single_line
%div
@@ -47,44 +47,53 @@
%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
- –
- #{@milestone.open_items_count} open
-
- %span.light #{@milestone.percent_complete}% complete
- %span.pull-right= @milestone.expires_at
+ .milestone-summary
+ %h4 Progress
+ %strong= @milestone.issues.count
+ issues:
+ %span.milestone-stat
+ %strong= @milestone.open_items_count
+ open and
+ %strong= @milestone.closed_items_count
+ 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 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"
+ %span.pull-right.tab-merge-requests-buttons.hidden
+ - 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"
+
= milestone_progress_bar(@milestone)
%ul.nav-links.no-top.no-bottom
%li.active
- = link_to '#tab-issues', 'data-toggle' => 'tab' do
+ = link_to '#tab-issues', 'data-toggle' => 'tab', 'data-show' => '.tab-issues-buttons' do
Issues
%span.badge= @issues.count
%li
- = link_to '#tab-merge-requests', 'data-toggle' => 'tab' do
+ = link_to '#tab-merge-requests', 'data-toggle' => 'tab', 'data-show' => '.tab-merge-requests-buttons' do
Merge Requests
%span.badge= @merge_requests.count
%li
= link_to '#tab-participants', 'data-toggle' => 'tab' do
Participants
%span.badge= @users.count
+ %li
+ = link_to '#tab-labels', 'data-toggle' => 'tab', 'data-show' => '.tab-issues-buttons' do
+ Labels
+ %span.badge= @labels.count
-.tab-content
+.tab-content.milestone-content
.tab-pane.active#tab-issues
- .gray-content-block.middle-block
- .pull-right
- - 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')
@@ -94,14 +103,6 @@
= render('issues', title: 'Completed Issues (closed)', issues: @issues.closed, id: 'closed')
.tab-pane#tab-merge-requests
- .gray-content-block.middle-block
- .pull-right
- - 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')
@@ -117,10 +118,6 @@
= render 'merge_request', merge_request: merge_request
.tab-pane#tab-participants
- .gray-content-block.middle-block
- .oneline
- All participants to this milestone
-
%ul.bordered-list
- @users.each do |user|
%li
@@ -129,3 +126,18 @@
%strong= truncate(user.name, lenght: 40)
%br
%small.cgray= user.username
+
+ .tab-pane#tab-labels
+ %ul.bordered-list.manage-labels-list
+ - @labels.each do |label|
+ %li
+ = render_colored_label(label)
+ - args = [@milestone.project.namespace, @milestone.project, milestone_title: @milestone.title, label_name: label.title]
+ - options = args.extract_options!
+
+ %span.issues-count
+ = link_to namespace_project_issues_path(*args, options.merge(state: 'opened')) do
+ = pluralize label.open_issues_count, 'open issue'
+ %span.issues-count
+ = link_to namespace_project_issues_path(*args, options.merge(state: 'closed')) do
+ = pluralize label.closed_issues_count, 'closed issue'
diff --git a/app/views/projects/notes/_diff_notes_with_reply.html.haml b/app/views/projects/notes/_diff_notes_with_reply.html.haml
index c731baf0a6..11f9859a90 100644
--- a/app/views/projects/notes/_diff_notes_with_reply.html.haml
+++ b/app/views/projects/notes/_diff_notes_with_reply.html.haml
@@ -7,7 +7,7 @@
%i.fa.fa-comment
= notes.count
%td.notes_content
- %ul.notes{ rel: note.discussion_id }
+ %ul.notes{ data: { discussion_id: note.discussion_id } }
= render notes
.discussion-reply-holder
= link_to_reply_diff(note)
diff --git a/app/views/projects/notes/_diff_notes_with_reply_parallel.html.haml b/app/views/projects/notes/_diff_notes_with_reply_parallel.html.haml
index c6726cbafa..bb761ed2f9 100644
--- a/app/views/projects/notes/_diff_notes_with_reply_parallel.html.haml
+++ b/app/views/projects/notes/_diff_notes_with_reply_parallel.html.haml
@@ -8,7 +8,7 @@
%i.fa.fa-comment
= notes_left.count
%td.notes_content.parallel.old
- %ul.notes{ rel: note1.discussion_id }
+ %ul.notes{ data: { discussion_id: note1.discussion_id } }
= render notes_left
.discussion-reply-holder
@@ -23,7 +23,7 @@
%i.fa.fa-comment
= notes_right.count
%td.notes_content.parallel.new
- %ul.notes{ rel: note2.discussion_id }
+ %ul.notes{ data: { discussion_id: note2.discussion_id } }
= render notes_right
.discussion-reply-holder
@@ -31,4 +31,3 @@
- else
%td.notes_line.new= ""
%td.notes_content.parallel.new= ""
-
diff --git a/app/views/projects/notes/_note.html.haml b/app/views/projects/notes/_note.html.haml
index 922535e5c4..e858c41283 100644
--- a/app/views/projects/notes/_note.html.haml
+++ b/app/views/projects/notes/_note.html.haml
@@ -1,4 +1,4 @@
-%li.timeline-entry{ id: dom_id(note), class: [dom_class(note), "note-row-#{note.id}", ('system-note' if note.system)], data: { discussion: note.discussion_id } }
+%li.timeline-entry{ id: dom_id(note), class: [dom_class(note), "note-row-#{note.id}", ('system-note' if note.system)] }
.timeline-entry-inner
.timeline-icon
%a{href: user_path(note.author)}
diff --git a/app/views/projects/notes/_notes_with_form.html.haml b/app/views/projects/notes/_notes_with_form.html.haml
index eb378b4260..910eb6cf66 100644
--- a/app/views/projects/notes/_notes_with_form.html.haml
+++ b/app/views/projects/notes/_notes_with_form.html.haml
@@ -5,6 +5,16 @@
.js-main-target-form
- if can? current_user, :create_note, @project
= render "projects/notes/form", view: diff_view
+- else
+ .disabled-comment-area
+ .disabled-profile
+ .disabled-comment
+ %span
+ Please
+ = link_to "register",new_user_session_path
+ or
+ = link_to "login",new_user_session_path
+ to post a comment
:javascript
var notes = new Notes("#{namespace_project_notes_path(namespace_id: @project.namespace, target_id: @noteable.id, target_type: @noteable.class.name.underscore)}", #{@notes.map(&:id).to_json}, #{Time.now.to_i}, "#{diff_view}")
diff --git a/app/views/projects/notes/discussions/_commit.html.haml b/app/views/projects/notes/discussions/_commit.html.haml
index 6903fad4a0..3da2f2060b 100644
--- a/app/views/projects/notes/discussions/_commit.html.haml
+++ b/app/views/projects/notes/discussions/_commit.html.haml
@@ -20,8 +20,7 @@
= render "projects/notes/discussions/diff", discussion_notes: discussion_notes, note: note
- else
.panel.panel-default
- .notes{ rel: discussion_notes.first.discussion_id }
+ .notes{ data: { discussion_id: discussion_notes.first.discussion_id } }
= render discussion_notes
.discussion-reply-holder
= link_to_reply_diff(discussion_notes.first)
-
diff --git a/app/views/projects/project_members/_group_members.html.haml b/app/views/projects/project_members/_group_members.html.haml
index 1c2458fa14..c53033e367 100644
--- a/app/views/projects/project_members/_group_members.html.haml
+++ b/app/views/projects/project_members/_group_members.html.haml
@@ -5,7 +5,7 @@
%small
(#{members.count})
- if can?(current_user, :admin_group_member, @group)
- .pull-right
+ .controls
= link_to group_group_members_path(@group), class: 'btn' do
= icon('pencil-square-o')
Manage group members
diff --git a/app/views/projects/project_members/_team.html.haml b/app/views/projects/project_members/_team.html.haml
index ccddab13aa..e8dce30425 100644
--- a/app/views/projects/project_members/_team.html.haml
+++ b/app/views/projects/project_members/_team.html.haml
@@ -4,7 +4,7 @@
project members
%small
(#{members.count})
- .pull-right
+ .controls
= form_tag namespace_project_project_members_path(@project.namespace, @project), method: :get, class: 'form-inline member-search-form' do
.form-group
= search_field_tag :search, params[:search], { placeholder: 'Find existing member by name', class: 'form-control', spellcheck: false }
diff --git a/app/views/projects/project_members/index.html.haml b/app/views/projects/project_members/index.html.haml
index 6239a14890..0f8848a5cb 100644
--- a/app/views/projects/project_members/index.html.haml
+++ b/app/views/projects/project_members/index.html.haml
@@ -1,13 +1,12 @@
- page_title "Members"
= render "header_title"
-- @blank_container = true
.project-members-page.prepend-top-default
- if can?(current_user, :admin_project_member, @project)
.panel.panel-default
.panel-heading
Add new user to project
- .pull-right
+ .controls
= link_to import_namespace_project_project_members_path(@project.namespace, @project), class: "btn btn-grouped", title: "Import members from another project" do
Import members
.panel-body
diff --git a/app/views/projects/show.atom.builder b/app/views/projects/show.atom.builder
index d676221910..9b3d3f069d 100644
--- a/app/views/projects/show.atom.builder
+++ b/app/views/projects/show.atom.builder
@@ -4,7 +4,7 @@ xml.feed "xmlns" => "http://www.w3.org/2005/Atom", "xmlns:media" => "http://sear
xml.link href: namespace_project_url(@project.namespace, @project, format: :atom, private_token: current_user.try(:private_token)), rel: "self", type: "application/atom+xml"
xml.link href: namespace_project_url(@project.namespace, @project), rel: "alternate", type: "text/html"
xml.id namespace_project_url(@project.namespace, @project)
- xml.updated @events.latest_update_time.xmlschema if @events.any?
+ xml.updated @events[0].updated_at.xmlschema if @events[0]
@events.each do |event|
event_to_atom(xml, event)
diff --git a/app/views/projects/tags/destroy.js.haml b/app/views/projects/tags/destroy.js.haml
new file mode 100644
index 0000000000..ffeacb5a00
--- /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/tree/_readme.html.haml b/app/views/projects/tree/_readme.html.haml
index 3c5edf4b03..baaa2caa6d 100644
--- a/app/views/projects/tree/_readme.html.haml
+++ b/app/views/projects/tree/_readme.html.haml
@@ -1,7 +1,7 @@
%article.file-holder.readme-holder
.file-title
= blob_icon readme.mode, readme.name
- = link_to namespace_project_blob_path(@project.namespace, @project, tree_join(@repository.root_ref, readme.name)) do
+ = link_to namespace_project_blob_path(@project.namespace, @project, tree_join(@repository.root_ref, @path, readme.name)) do
%strong
= readme.name
.file-content.wiki
diff --git a/app/views/projects/tree/_tree_header.html.haml b/app/views/projects/tree/_tree_header.html.haml
index 3343288ad2..3eb626e6dc 100644
--- a/app/views/projects/tree/_tree_header.html.haml
+++ b/app/views/projects/tree/_tree_header.html.haml
@@ -40,7 +40,7 @@
- continue_params = { to: namespace_project_new_blob_path(@project.namespace, @project, @id),
notice: edit_in_new_fork_notice,
notice_now: edit_in_new_fork_notice_now }
- - fork_path = namespace_project_fork_path(@project.namespace, @project, namespace_key: current_user.namespace.id,
+ - fork_path = namespace_project_forks_path(@project.namespace, @project, namespace_key: current_user.namespace.id,
continue: continue_params)
= link_to fork_path, method: :post do
= icon('pencil fw')
@@ -49,7 +49,7 @@
- continue_params = { to: request.fullpath,
notice: edit_in_new_fork_notice + " Try to upload a file again.",
notice_now: edit_in_new_fork_notice_now }
- - fork_path = namespace_project_fork_path(@project.namespace, @project, namespace_key: current_user.namespace.id,
+ - fork_path = namespace_project_forks_path(@project.namespace, @project, namespace_key: current_user.namespace.id,
continue: continue_params)
= link_to fork_path, method: :post do
= icon('file fw')
@@ -58,7 +58,7 @@
- continue_params = { to: request.fullpath,
notice: edit_in_new_fork_notice + " Try to create a new directory again.",
notice_now: edit_in_new_fork_notice_now }
- - fork_path = namespace_project_fork_path(@project.namespace, @project, namespace_key: current_user.namespace.id,
+ - fork_path = namespace_project_forks_path(@project.namespace, @project, namespace_key: current_user.namespace.id,
continue: continue_params)
= link_to fork_path, method: :post do
= icon('folder fw')
diff --git a/app/views/projects/variables/show.html.haml b/app/views/projects/variables/show.html.haml
index e80dffc1ce..efe1e6f24c 100644
--- a/app/views/projects/variables/show.html.haml
+++ b/app/views/projects/variables/show.html.haml
@@ -3,9 +3,11 @@
Secret Variables
%p.light
- These variables will be set to environment by the runner and will be hidden in the build log.
+ These variables will be set to environment by the runner.
%br
So you can use them for passwords, secret keys or whatever you want.
+ %br
+ The value of the variable can be visible in build log if explicitly asked to do so.
%hr
diff --git a/app/views/projects/wikis/_main_links.html.haml b/app/views/projects/wikis/_main_links.html.haml
index 29bf5d62ab..2b91b7e8f6 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 69ba301e23..a722fbc535 100644
--- a/app/views/projects/wikis/_nav.html.haml
+++ b/app/views/projects/wikis/_nav.html.haml
@@ -1,12 +1,4 @@
-.project-issuable-filter
- .controls
- - if can?(current_user, :create_wiki, @project)
- = link_to '#modal-new-wiki', class: "add-new-wiki btn btn-new", "data-toggle" => "modal" do
- %i.fa.fa-plus
- New Page
-
- = render 'projects/wikis/new'
-
+.top-area
%ul.nav-links
= nav_link(html_options: {class: params[:id] == 'home' ? 'active' : '' }) do
= link_to 'Home', namespace_project_wiki_path(@project.namespace, @project, :home)
@@ -17,3 +9,11 @@
= nav_link(path: 'wikis#git_access') do
= link_to namespace_project_wikis_git_access_path(@project.namespace, @project) do
Git Access
+
+ .nav-controls
+ - if can?(current_user, :create_wiki, @project)
+ = link_to '#modal-new-wiki', class: "add-new-wiki btn btn-new", "data-toggle" => "modal" do
+ = icon('plus')
+ New Page
+
+= render 'projects/wikis/new'
diff --git a/app/views/projects/wikis/_new.html.haml b/app/views/projects/wikis/_new.html.haml
index 53b37b1104..919daf0a7b 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 23f64fbbd1..4dd818c7f6 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
+ ·
+ 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 4322146ce3..dcaddae2b0 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
+ ·
+ History
.table-holder
%table.table
diff --git a/app/views/projects/wikis/pages.html.haml b/app/views/projects/wikis/pages.html.haml
index aae1ad69ad..92b494a513 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 309d40f52b..067fb7f8f5 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
·
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/results/_snippet_blob.html.haml b/app/views/search/results/_snippet_blob.html.haml
index dcd6119971..6b77d24f50 100644
--- a/app/views/search/results/_snippet_blob.html.haml
+++ b/app/views/search/results/_snippet_blob.html.haml
@@ -1,46 +1,50 @@
+- snippet_blob = chunk_snippet(snippet_blob, @search_term)
+- snippet = snippet_blob[:snippet_object]
+- snippet_chunks = snippet_blob[:snippet_chunks]
+
.search-result-row
%span
- = snippet_blob[:snippet_object].title
+ = snippet.title
by
- = link_to user_snippets_path(snippet_blob[:snippet_object].author) do
- = image_tag avatar_icon(snippet_blob[:snippet_object].author_email), class: "avatar avatar-inline s16", alt: ''
- = snippet_blob[:snippet_object].author_name
- %span.light #{time_ago_with_tooltip(snippet_blob[:snippet_object].created_at)}
+ = link_to user_snippets_path(snippet.author) do
+ = image_tag avatar_icon(snippet.author_email), class: "avatar avatar-inline s16", alt: ''
+ = snippet.author_name
+ %span.light #{time_ago_with_tooltip(snippet.created_at)}
%h4.snippet-title
- - snippet_path = reliable_snippet_path(snippet_blob[:snippet_object])
+ - snippet_path = reliable_snippet_path(snippet)
= link_to snippet_path do
.file-holder
.file-title
%i.fa.fa-file
- %strong= snippet_blob[:snippet_object].file_name
- - if markup?(snippet_blob[:snippet_object].file_name)
+ %strong= snippet.file_name
+ - if markup?(snippet.file_name)
.file-content.wiki
- - snippet_blob[:snippet_chunks].each do |snippet|
- - unless snippet[:data].empty?
- = render_markup(snippet_blob[:snippet_object].file_name, snippet[:data])
+ - snippet_chunks.each do |chunk|
+ - unless chunk[:data].empty?
+ = render_markup(snippet.file_name, chunk[:data])
- else
.file-content.code
.nothing-here-block Empty file
- else
.file-content.code.js-syntax-highlight
.line-numbers
- - snippet_blob[:snippet_chunks].each do |snippet|
- - unless snippet[:data].empty?
- - snippet[:data].lines.to_a.size.times do |index|
- - offset = defined?(snippet[:start_line]) ? snippet[:start_line] : 1
+ - snippet_chunks.each do |chunk|
+ - unless chunk[:data].empty?
+ - chunk[:data].lines.to_a.size.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
%i.fa.fa-link
= i
- - unless snippet == snippet_blob[:snippet_chunks].last
+ - unless snippet == snippet_chunks.last
%a.diff-line-num
= "."
%pre.code
%code
- - snippet_blob[:snippet_chunks].each do |snippet|
- - unless snippet[:data].empty?
- = snippet[:data]
- - unless snippet == snippet_blob[:snippet_chunks].last
+ - snippet_chunks.each do |chunk|
+ - unless chunk[:data].empty?
+ = chunk[:data]
+ - unless chunk == snippet_chunks.last
%a
= "..."
- else
diff --git a/app/views/shared/_file_highlight.html.haml b/app/views/shared/_file_highlight.html.haml
index ee242c94db..57856031d6 100644
--- a/app/views/shared/_file_highlight.html.haml
+++ b/app/views/shared/_file_highlight.html.haml
@@ -1,7 +1,7 @@
.file-content.code.js-syntax-highlight
.line-numbers
- if blob.data.present?
- - blob.data.lines.each_index do |index|
+ - blob.data.each_line.each_with_index do |_, index|
- offset = defined?(first_line_number) ? first_line_number : 1
- i = index + offset
-# We're not using `link_to` because it is too slow once we get to thousands of lines.
diff --git a/app/views/shared/_import_form.html.haml b/app/views/shared/_import_form.html.haml
index 285af56ad7..627814bcfa 100644
--- a/app/views/shared/_import_form.html.haml
+++ b/app/views/shared/_import_form.html.haml
@@ -11,6 +11,6 @@
%li
If your HTTP repository is not publicly accessible, add authentication information to the URL: https://username:password@gitlab.company.com/group/project.git
.
%li
- The import will time out after 4 minutes. For big repositories, use a clone/push combination.
+ The import will time out after 15 minutes. For repositories that take longer, use a clone/push combination.
%li
To migrate an SVN repository, check out #{link_to "this document", "http://doc.gitlab.com/ce/workflow/importing/migrating_from_svn.html"}.
diff --git a/app/views/shared/_label_row.html.haml b/app/views/shared/_label_row.html.haml
new file mode 100644
index 0000000000..8134b15d24
--- /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/_logo.svg b/app/views/shared/_logo.svg
index 3d279ec228..b07f1c5603 100644
--- a/app/views/shared/_logo.svg
+++ b/app/views/shared/_logo.svg
@@ -1,21 +1,9 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
diff --git a/app/views/shared/_milestones_filter.html.haml b/app/views/shared/_milestones_filter.html.haml
index f77feeb79c..cf16c203f9 100644
--- a/app/views/shared/_milestones_filter.html.haml
+++ b/app/views/shared/_milestones_filter.html.haml
@@ -1,11 +1,10 @@
-.milestones-filters
- %ul.nav-links
- %li{class: ("active" if params[:state].blank? || params[:state] == 'opened')}
- = link_to milestones_filter_path(state: 'opened') do
- Open
- %li{class: ("active" if params[:state] == 'closed')}
- = link_to milestones_filter_path(state: 'closed') do
- Closed
- %li{class: ("active" if params[:state] == 'all')}
- = link_to milestones_filter_path(state: 'all') do
- All
+%ul.nav-links
+ %li{class: ("active" if params[:state].blank? || params[:state] == 'opened')}
+ = link_to milestones_filter_path(state: 'opened') do
+ Open
+ %li{class: ("active" if params[:state] == 'closed')}
+ = link_to milestones_filter_path(state: 'closed') do
+ Closed
+ %li{class: ("active" if params[:state] == 'all')}
+ = link_to milestones_filter_path(state: 'all') do
+ All
diff --git a/app/views/shared/_new_project_item_select.html.haml b/app/views/shared/_new_project_item_select.html.haml
index c4431d6692..1c58345278 100644
--- a/app/views/shared/_new_project_item_select.html.haml
+++ b/app/views/shared/_new_project_item_select.html.haml
@@ -1,6 +1,6 @@
- if @projects.any?
- .prepend-left-10.new-project-item-select-holder
- = project_select_tag :project_path, class: "new-project-item-select", data: { include_groups: local_assigns[:include_groups] }
+ .prepend-left-10.project-item-select-holder
+ = project_select_tag :project_path, class: "project-item-select", data: { include_groups: local_assigns[:include_groups], order_by: 'last_activity_at' }
%a.btn.btn-new.new-project-item-select-button
= icon('plus')
= local_assigns[:label]
@@ -8,12 +8,12 @@
:javascript
$('.new-project-item-select-button').on('click', function() {
- $('.new-project-item-select').select2('open');
+ $('.project-item-select').select2('open');
});
var relativePath = '#{local_assigns[:path]}';
- $('.new-project-item-select').on('click', function() {
+ $('.project-item-select').on('click', function() {
window.location = $(this).val() + '/' + relativePath;
});
diff --git a/app/views/shared/_project_limit.html.haml b/app/views/shared/_project_limit.html.haml
index 960ff00b49..f4eb8e491b 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/_promo.html.haml b/app/views/shared/_promo.html.haml
index 3596aabe30..09edf4000d 100644
--- a/app/views/shared/_promo.html.haml
+++ b/app/views/shared/_promo.html.haml
@@ -1,5 +1,5 @@
.gitlab-promo
= link_to 'Homepage', promo_url
- = link_to "Blog", promo_url + '/blog/'
- = link_to "@gitlab", "https://twitter.com/gitlab"
- = link_to "Requests", "http://feedback.gitlab.com/"
+ = link_to 'Blog', promo_url + '/blog/'
+ = link_to '@gitlab', 'https://twitter.com/gitlab'
+ = link_to 'Requests', 'https://gitlab.com/gitlab-org/gitlab-ce/blob/master/CONTRIBUTING.md#feature-proposals'
diff --git a/app/views/shared/_sort_dropdown.html.haml b/app/views/shared/_sort_dropdown.html.haml
index af3d35de32..e3a6a5a68b 100644
--- a/app/views/shared/_sort_dropdown.html.haml
+++ b/app/views/shared/_sort_dropdown.html.haml
@@ -1,6 +1,6 @@
.dropdown.inline.prepend-left-10
%button.dropdown-toggle.btn{type: 'button', 'data-toggle' => 'dropdown'}
- %span.light sort:
+ %span.light
- if @sort.present?
= sort_options_hash[@sort]
- else
@@ -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 778b20fb4f..289b0bfe1e 100644
--- a/app/views/shared/groups/_group.html.haml
+++ b/app/views/shared/groups/_group.html.haml
@@ -1,5 +1,8 @@
- group_member = local_assigns[:group_member]
-%li
+- css_class = '' unless local_assigns[:css_class]
+- css_class += " no-description" if group.description.blank?
+
+%li.group-row{ class: css_class }
- if group_member
.controls.hidden-xs
- if can?(current_user, :admin_group, group)
@@ -9,7 +12,16 @@
= link_to leave_group_group_members_path(group), data: { confirm: leave_group_message(group.name) }, method: :delete, class: "btn-sm btn btn-grouped", title: 'Leave this group' do
%i.fa.fa-sign-out
- = image_tag group_icon(group), class: "avatar s46 hidden-xs"
+ .stats
+ %span
+ = icon('home')
+ = number_with_delimiter(group.projects.count)
+
+ %span
+ = icon('users')
+ = 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
@@ -17,5 +29,6 @@
as
%span #{group_member.human_access}
- %div.light
- #{pluralize(group.projects.count, "project")}, #{pluralize(group.users.count, "user")}
+ - if group.description.present?
+ .light
+ = markdown(group.description, pipeline: :description)
diff --git a/app/views/shared/issuable/_filter.html.haml b/app/views/shared/issuable/_filter.html.haml
index 8d6f47b38e..e55159d996 100644
--- a/app/views/shared/issuable/_filter.html.haml
+++ b/app/views/shared/issuable/_filter.html.haml
@@ -1,32 +1,5 @@
.issues-filters
- .issues-state-filters
- %ul.nav-links
- - if defined?(type) && type == :merge_requests
- - page_context_word = 'merge requests'
- - else
- - page_context_word = 'issues'
- %li{class: ("active" if params[:state] == 'opened')}
- = link_to page_filter_path(state: 'opened'), title: "Filter by #{page_context_word} that are currently opened." do
- #{state_filters_text_for(:opened, @project)}
-
- - if defined?(type) && type == :merge_requests
- %li{class: ("active" if params[:state] == 'merged')}
- = link_to page_filter_path(state: 'merged'), title: 'Filter by merge requests that are currently merged.' do
- #{state_filters_text_for(:merged, @project)}
-
- %li{class: ("active" if params[:state] == 'closed')}
- = link_to page_filter_path(state: 'closed'), title: 'Filter by merge requests that are currently closed and unmerged.' do
- #{state_filters_text_for(:closed, @project)}
- - else
- %li{class: ("active" if params[:state] == 'closed')}
- = link_to page_filter_path(state: 'closed'), title: 'Filter by issues that are currently closed.' do
- #{state_filters_text_for(:closed, @project)}
-
- %li{class: ("active" if params[:state] == 'all')}
- = link_to page_filter_path(state: 'all'), title: "Show all #{page_context_word}." do
- #{state_filters_text_for(:all, @project)}
-
- .issues-details-filters.gray-content-block
+ .issues-details-filters.gray-content-block.second-block
= form_tag page_filter_path(without: [:assignee_id, :author_id, :milestone_title, :label_name]), method: :get, class: 'filter-form' do
- if controller.controller_name == 'issues' && can?(current_user, :admin_issue, @project)
.check-all-holder
@@ -68,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/_nav.html.haml b/app/views/shared/issuable/_nav.html.haml
new file mode 100644
index 0000000000..a6970b7eeb
--- /dev/null
+++ b/app/views/shared/issuable/_nav.html.haml
@@ -0,0 +1,25 @@
+%ul.nav-links.issues-state-filters
+ - if defined?(type) && type == :merge_requests
+ - page_context_word = 'merge requests'
+ - else
+ - page_context_word = 'issues'
+ %li{class: ("active" if params[:state] == 'opened')}
+ = link_to page_filter_path(state: 'opened'), title: "Filter by #{page_context_word} that are currently opened." do
+ #{state_filters_text_for(:opened, @project)}
+
+ - if defined?(type) && type == :merge_requests
+ %li{class: ("active" if params[:state] == 'merged')}
+ = link_to page_filter_path(state: 'merged'), title: 'Filter by merge requests that are currently merged.' do
+ #{state_filters_text_for(:merged, @project)}
+
+ %li{class: ("active" if params[:state] == 'closed')}
+ = link_to page_filter_path(state: 'closed'), title: 'Filter by merge requests that are currently closed and unmerged.' do
+ #{state_filters_text_for(:closed, @project)}
+ - else
+ %li{class: ("active" if params[:state] == 'closed')}
+ = link_to page_filter_path(state: 'closed'), title: 'Filter by issues that are currently closed.' do
+ #{state_filters_text_for(:closed, @project)}
+
+ %li{class: ("active" if params[:state] == 'all')}
+ = link_to page_filter_path(state: 'all'), title: "Show all #{page_context_word}." do
+ #{state_filters_text_for(:all, @project)}
diff --git a/app/views/shared/issuable/_participants.html.haml b/app/views/shared/issuable/_participants.html.haml
index da6bacbb74..f1d92ef48b 100644
--- a/app/views/shared/issuable/_participants.html.haml
+++ b/app/views/shared/issuable/_participants.html.haml
@@ -1,5 +1,10 @@
.block.participants
- .title
+ .sidebar-collapsed-icon
+ = icon('users')
+ %span
+ = participants.count
+ .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/_search_form.html.haml b/app/views/shared/issuable/_search_form.html.haml
index 3a5ad00aa9..afad48499b 100644
--- a/app/views/shared/issuable/_search_form.html.haml
+++ b/app/views/shared/issuable/_search_form.html.haml
@@ -1,9 +1,8 @@
-= form_tag(path, method: :get, id: "issue_search_form", class: 'pull-left issue-search-form') do
- .append-right-10.hidden-xs.hidden-sm
- = search_field_tag :issue_search, params[:issue_search], { placeholder: 'Filter by title or description', class: 'form-control issue_search search-text-input', spellcheck: false }
- = hidden_field_tag :state, params['state']
- = hidden_field_tag :scope, params['scope']
- = hidden_field_tag :assignee_id, params['assignee_id']
- = hidden_field_tag :author_id, params['author_id']
- = hidden_field_tag :milestone_id, params['milestone_id']
- = hidden_field_tag :label_id, params['label_id']
+= form_tag(path, method: :get, id: "issue_search_form", class: 'issue-search-form') do
+ = search_field_tag :issue_search, params[:issue_search], { placeholder: 'Filter by name ...', class: 'form-control issue_search search-text-input input-short', spellcheck: false }
+ = hidden_field_tag :state, params['state']
+ = hidden_field_tag :scope, params['scope']
+ = hidden_field_tag :assignee_id, params['assignee_id']
+ = hidden_field_tag :author_id, params['author_id']
+ = hidden_field_tag :milestone_id, params['milestone_id']
+ = hidden_field_tag :label_id, params['label_id']
diff --git a/app/views/shared/issuable/_sidebar.html.haml b/app/views/shared/issuable/_sidebar.html.haml
index 9f4a7098ea..36f0637788 100644
--- a/app/views/shared/issuable/_sidebar.html.haml
+++ b/app/views/shared/issuable/_sidebar.html.haml
@@ -1,85 +1,128 @@
-.issuable-sidebar.issuable-affix
- = form_for [@project.namespace.becomes(Namespace), @project, issuable], remote: true, html: {class: 'issuable-context-form inline-update js-issuable-update'} do |f|
- .block.assignee
- .title
- %label
- Assignee
- - if can?(current_user, :"admin_#{issuable.to_ability_name}", @project)
- .pull-right
- = link_to 'Edit', '#', class: 'edit-link'
- .value
- - if issuable.assignee
- %strong= link_to_member(@project, issuable.assignee, size: 24)
+%aside.right-sidebar{ class: sidebar_gutter_collapsed_class }
+ .issuable-sidebar
+ .block
+ %span.issuable-count.hide-collapsed.pull-left
+ = issuable.iid
+ of
+ = issuables_count(issuable)
+ %span.pull-right
+ %a.gutter-toggle{href: '#'}
+ = 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
- .light None
-
- .selectbox
- = 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
- .title
- %label
- Milestone
- - if can?(current_user, :"admin_#{issuable.to_ability_name}", @project)
- .pull-right
- = link_to 'Edit', '#', class: 'edit-link'
- .value
- - if issuable.milestone
- %span.back-to-milestone
- = link_to namespace_project_milestone_path(@project.namespace, @project, issuable.milestone) do
- %strong
- = icon('clock-o')
- = issuable.milestone.title
+ %a.btn.btn-default.disabled{href: '#'}
+ Prev
+ - if next_issuable = next_issuable_for(issuable)
+ = link_to 'Next', [@project.namespace.becomes(Namespace), @project, next_issuable], class: 'btn btn-default next-btn'
- else
- .light None
- .selectbox
- = 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'
+ %a.btn.btn-default.disabled{href: '#'}
+ Next
- - if issuable.project.labels.any?
- .block
- .title
- %label Labels
+ = form_for [@project.namespace.becomes(Namespace), @project, issuable], remote: true, html: {class: 'issuable-context-form inline-update js-issuable-update'} do |f|
+ .block.assignee
+ .sidebar-collapsed-icon
+ - if issuable.assignee
+ = link_to_member_avatar(issuable.assignee, size: 24)
+ - else
+ = icon('user')
+ .title.hide-collapsed
+ %label
+ Assignee
- if can?(current_user, :"admin_#{issuable.to_ability_name}", @project)
.pull-right
= link_to 'Edit', '#', class: 'edit-link'
- .value.issuable-show-labels
- - if issuable.labels.any?
- - issuable.labels.each do |label|
- = link_to_label(label)
+ .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)
+ %a.pull-right.cannot-be-merged{href: '#', data: {toggle: 'tooltip'}, title: 'Not allowed to merge'}
+ = icon('exclamation-triangle')
- else
.light None
- .selectbox
- = 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" }
- = render "shared/issuable/participants", participants: issuable.participants(current_user)
+ .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)
- - if current_user
- - subscribed = issuable.subscribed?(current_user)
- .block.light
- .title
- %label.light Notifications
- - subscribtion_status = subscribed ? 'subscribed' : 'unsubscribed'
- %button.btn.btn-block.btn-gray.subscribe-button{:type => 'button'}
- %span= subscribed ? 'Unsubscribe' : 'Subscribe'
- .subscription-status{data: {status: subscribtion_status}}
- .unsubscribed{class: ( 'hidden' if subscribed )}
- You're not receiving notifications from this thread.
- .subscribed{class: ( 'hidden' unless subscribed )}
- You're receiving notifications because you're subscribed to this thread.
+ .block.milestone
+ .sidebar-collapsed-icon
+ = icon('clock-o')
+ %span
+ - if issuable.milestone
+ = issuable.milestone.title
+ - else
+ No
+ .title.hide-collapsed
+ %label
+ Milestone
+ - if can?(current_user, :"admin_#{issuable.to_ability_name}", @project)
+ .pull-right
+ = link_to 'Edit', '#', class: 'edit-link'
+ .value.hide-collapsed
+ - if issuable.milestone
+ %span.back-to-milestone
+ = link_to namespace_project_milestone_path(@project.namespace, @project, issuable.milestone) do
+ %strong
+ = icon('clock-o')
+ = issuable.milestone.title
+ - else
+ .light None
+ .selectbox.hide-collapsed
+ = f.select(:milestone_id, milestone_options(issuable), { include_blank: true }, { class: 'select2 select2-compact js-select2 js-milestone', data: { placeholder: 'Select milestone' }})
+ = hidden_field_tag :issuable_context
+ = f.submit class: 'btn hide'
- - project_ref = cross_project_reference(@project, issuable)
- .block
- .title
- .cross-project-reference
- %span
- Reference:
- %cite{title: project_ref}
- = project_ref
- = clipboard_button(clipboard_text: project_ref)
+ - if issuable.project.labels.any?
+ .block.labels
+ .sidebar-collapsed-icon
+ = icon('tags')
+ %span
+ = issuable.labels.count
+ .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.hide-collapsed
+ - if issuable.labels.any?
+ - issuable.labels.each do |label|
+ = link_to_label(label, type: issuable.to_ability_name)
+ - else
+ .light None
+ .selectbox.hide-collapsed
+ = f.collection_select :label_ids, issuable.project.labels.all, :id, :name,
+ { selected: issuable.label_ids }, multiple: true, class: 'select2 js-select2', data: { placeholder: "Select labels" }
- :javascript
- new Subscription("#{toggle_subscription_path(issuable)}");
- new IssuableContext();
\ No newline at end of file
+ = render "shared/issuable/participants", participants: issuable.participants(current_user)
+ %hr
+ - if current_user
+ - subscribed = issuable.subscribed?(current_user)
+ .block.light
+ .sidebar-collapsed-icon
+ = icon('rss')
+ .title.hide-collapsed
+ %label.light Notifications
+ - subscribtion_status = subscribed ? 'subscribed' : 'unsubscribed'
+ %button.btn.btn-block.btn-gray.subscribe-button.hide-collapsed{:type => 'button'}
+ %span= subscribed ? 'Unsubscribe' : 'Subscribe'
+ .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 )}
+ You're receiving notifications because you're subscribed to this thread.
+
+ - project_ref = cross_project_reference(@project, issuable)
+ .block.project-reference
+ .sidebar-collapsed-icon
+ = clipboard_button(clipboard_text: project_ref)
+ .cross-project-reference.hide-collapsed
+ %span
+ Reference:
+ %cite{title: project_ref}
+ = project_ref
+ = clipboard_button(clipboard_text: project_ref)
+
+ :javascript
+ new Subscription("#{toggle_subscription_path(issuable)}");
+ new IssuableContext();
diff --git a/app/views/shared/projects/_list.html.haml b/app/views/shared/projects/_list.html.haml
index e5ffe1e29a..1d88bddcf4 100644
--- a/app/views/shared/projects/_list.html.haml
+++ b/app/views/shared/projects/_list.html.haml
@@ -1,21 +1,30 @@
- projects_limit = 20 unless local_assigns[:projects_limit]
- avatar = true unless local_assigns[:avatar] == false
+- use_creator_avatar = false unless local_assigns[:use_creator_avatar] == true
- stars = true unless local_assigns[:stars] == false
+- forks = false unless local_assigns[:forks] == true
- 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
%ul.projects-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
+ - 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
- %li.bottom.center
- .light
- #{projects_limit} of #{pluralize(projects.count, 'project')} displayed.
- = link_to '#', class: 'js-expand' do
- Show all
+ - 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
+ - else
+ .nothing-here-block No projects found
:javascript
new ProjectsList();
+ Dashboard.init();
diff --git a/app/views/shared/projects/_project.html.haml b/app/views/shared/projects/_project.html.haml
index 5db8056b77..97db5b1d41 100644
--- a/app/views/shared/projects/_project.html.haml
+++ b/app/views/shared/projects/_project.html.haml
@@ -1,9 +1,11 @@
- avatar = true unless local_assigns[:avatar] == false
- stars = true unless local_assigns[:stars] == false
+- forks = false unless local_assigns[:forks] == true
- ci = false unless local_assigns[:ci] == true
- skip_namespace = false unless local_assigns[:skip_namespace] == true
- css_class = '' unless local_assigns[:css_class]
-- css_class += " no-description" unless project.description.present?
+- 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']
- cache_key.push(ci_commit.status) if ci_commit
@@ -13,7 +15,10 @@
= link_to project_path(project), class: dom_class(project) do
- if avatar
.dash-project-avatar
- = project_icon(project, alt: '', class: 'avatar project-avatar s46')
+ - if use_creator_avatar
+ = 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.namespace-name
- if project.namespace && !skip_namespace
@@ -24,12 +29,20 @@
.project-controls
- if ci_commit
- = render_ci_status(ci_commit)
-
+ %span
+ = render_ci_status(ci_commit)
+ - if forks
+ %span
+ = icon('code-fork')
+ = project.forks_count
- if stars
%span
- %i.fa.fa-star
+ = icon('star')
= project.star_count
- - if project.description.present?
+ - if show_last_commit_as_description
+ .project-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
= markdown(project.description, pipeline: :description)
diff --git a/app/views/sherlock/transactions/_queries.html.haml b/app/views/sherlock/transactions/_queries.html.haml
index b7e0162e80..b8d93e9ff4 100644
--- a/app/views/sherlock/transactions/_queries.html.haml
+++ b/app/views/sherlock/transactions/_queries.html.haml
@@ -8,7 +8,7 @@
%tr
%th= t('sherlock.time')
%th= t('sherlock.query')
- %td
+ %th
%tbody
- @transaction.sorted_queries.each do |query|
%tr
diff --git a/app/views/users/show.atom.builder b/app/views/users/show.atom.builder
index 114d1e7a37..e9e466c635 100644
--- a/app/views/users/show.atom.builder
+++ b/app/views/users/show.atom.builder
@@ -4,7 +4,7 @@ xml.feed "xmlns" => "http://www.w3.org/2005/Atom", "xmlns:media" => "http://sear
xml.link href: user_url(@user, :atom), rel: "self", type: "application/atom+xml"
xml.link href: user_url(@user), rel: "alternate", type: "text/html"
xml.id user_url(@user)
- xml.updated @events.latest_update_time.xmlschema if @events.any?
+ xml.updated @events[0].updated_at.xmlschema if @events[0]
@events.each do |event|
event_to_atom(xml, event)
diff --git a/app/views/votes/_votes_block.html.haml b/app/views/votes/_votes_block.html.haml
index b1f8645eea..91c5b7eac5 100644
--- a/app/views/votes/_votes_block.html.haml
+++ b/app/views/votes/_votes_block.html.haml
@@ -7,7 +7,7 @@
- if current_user
.awards-controls
- %a.add-award{"data-toggle" => "dropdown", "data-target" => "#", "href" => "#"}
+ %a.add-award{"href" => "#"}
= icon('smile-o')
.emoji-menu
.emoji-menu-content
diff --git a/app/workers/new_note_worker.rb b/app/workers/new_note_worker.rb
new file mode 100644
index 0000000000..1b3232cd36
--- /dev/null
+++ b/app/workers/new_note_worker.rb
@@ -0,0 +1,12 @@
+class NewNoteWorker
+ include Sidekiq::Worker
+
+ sidekiq_options queue: :default
+
+ def perform(note_id, note_params)
+ note = Note.find(note_id)
+
+ NotificationService.new.new_note(note)
+ Notes::PostProcessService.new(note).execute
+ end
+end
diff --git a/app/workers/post_receive.rb b/app/workers/post_receive.rb
index 994b8e8ed3..14d7813412 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/project_destroy_worker.rb b/app/workers/project_destroy_worker.rb
new file mode 100644
index 0000000000..d06e448029
--- /dev/null
+++ b/app/workers/project_destroy_worker.rb
@@ -0,0 +1,17 @@
+class ProjectDestroyWorker
+ include Sidekiq::Worker
+
+ sidekiq_options queue: :default
+
+ def perform(project_id, user_id, params)
+ begin
+ project = Project.find(project_id)
+ rescue ActiveRecord::RecordNotFound
+ return
+ end
+
+ user = User.find(user_id)
+
+ ::Projects::DestroyService.new(project, user, params).execute
+ end
+end
diff --git a/app/workers/repository_fork_worker.rb b/app/workers/repository_fork_worker.rb
index 2f991c5233..2572b9d6d9 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.expire_emptiness_caches
project.import_finish
end
end
diff --git a/app/workers/repository_import_worker.rb b/app/workers/repository_import_worker.rb
index d18c0706b3..0b6f746e11 100644
--- a/app/workers/repository_import_worker.rb
+++ b/app/workers/repository_import_worker.rb
@@ -4,52 +4,21 @@ class RepositoryImportWorker
sidekiq_options queue: :gitlab_shell
+ attr_accessor :project, :current_user
+
def perform(project_id)
- project = Project.find(project_id)
+ @project = Project.find(project_id)
+ @current_user = @project.creator
- if project.import_url == Project::UNKNOWN_IMPORT_URL
- # In this case, we only want to import issues, not a repository.
- unless project.create_repository
- project.update(import_error: "The repository could not be created.")
- project.import_fail
- return
- end
- else
- begin
- gitlab_shell.import_repository(project.path_with_namespace, project.import_url)
- rescue Gitlab::Shell::Error => e
- project.update(import_error: e.message)
- project.import_fail
- return
- end
- end
+ result = Projects::ImportService.new(project, current_user).execute
- data_import_result =
- case project.import_type
- when 'github'
- Gitlab::GithubImport::Importer.new(project).execute
- when 'gitlab'
- Gitlab::GitlabImport::Importer.new(project).execute
- when 'bitbucket'
- Gitlab::BitbucketImport::Importer.new(project).execute
- when 'google_code'
- Gitlab::GoogleCodeImport::Importer.new(project).execute
- when 'fogbugz'
- Gitlab::FogbugzImport::Importer.new(project).execute
- else
- true
- end
-
- unless data_import_result
- project.update(import_error: "The remote issue data could not be imported.")
+ if result[:status] == :error
+ project.update(import_error: result[:message])
project.import_fail
return
end
- if project.import_type == 'bitbucket'
- Gitlab::BitbucketImport::KeyDeleter.new(project).execute
- end
-
+ project.repository.expire_emptiness_caches
project.import_finish
end
end
diff --git a/config/application.rb b/config/application.rb
index d255ff0719..28684a3e57 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
@@ -31,7 +33,7 @@ module Gitlab
config.encoding = "utf-8"
# Configure sensitive parameters which will be filtered from the log file.
- config.filter_parameters.push(:password, :password_confirmation, :private_token, :otp_attempt)
+ config.filter_parameters.push(:password, :password_confirmation, :private_token, :otp_attempt, :variables)
# Enable escaping HTML in JSON.
config.active_support.escape_html_entities_in_json = true
@@ -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,20 +54,6 @@ module Gitlab
config.action_view.sanitized_allowed_protocols = %w(smb)
- # Relative url support
- # Uncomment and customize the last line to run in a non-root path
- # WARNING: We recommend creating a FQDN to host GitLab in a root path instead of this.
- # Note that following settings need to be changed for this to work.
- # 1) In your application.rb file: config.relative_url_root = "/gitlab"
- # 2) In your gitlab.yml file: relative_url_root: /gitlab
- # 3) In your unicorn.rb: ENV['RAILS_RELATIVE_URL_ROOT'] = "/gitlab"
- # 4) In ../gitlab-shell/config.yml: gitlab_url: "http://127.0.0.1/gitlab"
- # 5) In lib/support/nginx/gitlab : do not use asset gzipping, remove block starting with "location ~ ^/(assets)/"
- #
- # To update the path, run: sudo -u git -H bundle exec rake assets:precompile RAILS_ENV=production
- #
- # config.relative_url_root = "/gitlab"
-
config.middleware.use Rack::Attack
# Allow access to GitLab API from other domains
@@ -95,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
diff --git a/config/database.yml.env b/config/database.yml.env
index b2ff23cb5a..1e35651c9a 100644
--- a/config/database.yml.env
+++ b/config/database.yml.env
@@ -1,9 +1,17 @@
<%= ENV['RAILS_ENV'] %>:
+ ## Connection information
+ # Please be aware that the DATABASE_URL environment variable will take
+ # precedence over the following 6 parameters. For more information, see
+ # doc/administration/environment_variables.md
adapter: <%= ENV['GITLAB_DATABASE_ADAPTER'] || 'postgresql' %>
- encoding: <%= ENV['GITLAB_DATABASE_ENCODING'] || 'unicode' %>
database: <%= ENV['GITLAB_DATABASE_DATABASE'] || "gitlab_#{ENV['RAILS_ENV']}" %>
- pool: <%= ENV['GITLAB_DATABASE_POOL'] || '10' %>
username: <%= ENV['GITLAB_DATABASE_USERNAME'] || 'root' %>
password: <%= ENV['GITLAB_DATABASE_PASSWORD'] || '' %>
host: <%= ENV['GITLAB_DATABASE_HOST'] || 'localhost' %>
port: <%= ENV['GITLAB_DATABASE_PORT'] || '5432' %>
+
+ ## Behavior information
+ # The following parameters will be used even if you're using the DATABASE_URL
+ # environment variable.
+ encoding: <%= ENV['GITLAB_DATABASE_ENCODING'] || 'unicode' %>
+ pool: <%= ENV['GITLAB_DATABASE_POOL'] || '10' %>
diff --git a/config/environments/development.rb b/config/environments/development.rb
index 257c163720..689694a348 100644
--- a/config/environments/development.rb
+++ b/config/environments/development.rb
@@ -16,6 +16,9 @@ Rails.application.configure do
# Print deprecation notices to the Rails logger
config.active_support.deprecation = :log
+ # Raise an error on page load if there are pending migrations
+ config.active_record.migration_error = :page_load
+
# Only use best-standards-support built into browsers
config.action_dispatch.best_standards_support = :builtin
diff --git a/config/gitlab.yml.example b/config/gitlab.yml.example
index d6e2c9380a..05f127d622 100644
--- a/config/gitlab.yml.example
+++ b/config/gitlab.yml.example
@@ -38,8 +38,12 @@ production: &base
# Otherwise, ssh host will be set to the `host:` value above
# ssh_host: ssh.host_example.com
- # WARNING: See config/application.rb under "Relative url support" for the list of
- # other files that need to be changed for relative url support
+ # 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
+ #
# relative_url_root: /gitlab
# Uncomment and customize if you can't use the default user to run GitLab (default: 'git')
@@ -284,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 04a7c16ebd..d8170557f7 100644
--- a/config/initializers/1_settings.rb
+++ b/config/initializers/1_settings.rb
@@ -176,7 +176,7 @@ Settings.gitlab['signin_enabled'] ||= true if Settings.gitlab['signin_enabled'].
Settings.gitlab['twitter_sharing_enabled'] ||= true if Settings.gitlab['twitter_sharing_enabled'].nil?
Settings.gitlab['restricted_visibility_levels'] = Settings.send(:verify_constant_array, Gitlab::VisibilityLevel, Settings.gitlab['restricted_visibility_levels'], [])
Settings.gitlab['username_changing_enabled'] = true if Settings.gitlab['username_changing_enabled'].nil?
-Settings.gitlab['issue_closing_pattern'] = '((?:[Cc]los(?:e[sd]?|ing)|[Ff]ix(?:e[sd]|ing)?|[Rr]esolv(?:e[sd]?|ing)) +(?:(?:issues? +)?%{issue_ref}(?:(?:, *| +and +)?)|([A-Z]*-\d*))+)' if Settings.gitlab['issue_closing_pattern'].nil?
+Settings.gitlab['issue_closing_pattern'] = '((?:[Cc]los(?:e[sd]?|ing)|[Ff]ix(?:e[sd]|ing)?|[Rr]esolv(?:e[sd]?|ing)) +(?:(?:issues? +)?%{issue_ref}(?:(?:, *| +and +)?)|([A-Z][A-Z0-9_]+-\d+))+)' if Settings.gitlab['issue_closing_pattern'].nil?
Settings.gitlab['default_projects_features'] ||= {}
Settings.gitlab['webhook_timeout'] ||= 10
Settings.gitlab['max_attachment_size'] ||= 10
diff --git a/config/initializers/2_app.rb b/config/initializers/2_app.rb
index 35b150c992..bd74f90e7d 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/haml.rb b/config/initializers/haml.rb
index 7e8ddb3716..1516476815 100644
--- a/config/initializers/haml.rb
+++ b/config/initializers/haml.rb
@@ -1 +1,7 @@
Haml::Template.options[:ugly] = true
+
+# Remove the `:coffee` and `:coffeescript` filters
+#
+# See https://git.io/vztMu and http://stackoverflow.com/a/17571242/223897
+Haml::Filters.remove_filter('coffee')
+Haml::Filters.remove_filter('coffeescript')
diff --git a/config/initializers/monkey_patch.rb b/config/initializers/monkey_patch.rb
new file mode 100644
index 0000000000..62b05a5528
--- /dev/null
+++ b/config/initializers/monkey_patch.rb
@@ -0,0 +1,48 @@
+## This patch is from rails 4.2-stable. Remove it when 4.2.6 is released
+## https://github.com/rails/rails/issues/21108
+
+module ActiveRecord
+ module ConnectionAdapters
+ class AbstractMysqlAdapter < AbstractAdapter
+ # SHOW VARIABLES LIKE 'name'
+ def show_variable(name)
+ variables = select_all("select @@#{name} as 'Value'", 'SCHEMA')
+ variables.first['Value'] unless variables.empty?
+ rescue ActiveRecord::StatementInvalid
+ nil
+ end
+
+
+ # MySQL is too stupid to create a temporary table for use subquery, so we have
+ # to give it some prompting in the form of a subsubquery. Ugh!
+ def subquery_for(key, select)
+ subsubselect = select.clone
+ subsubselect.projections = [key]
+
+ subselect = Arel::SelectManager.new(select.engine)
+ subselect.project Arel.sql(key.name)
+ # Materialized subquery by adding distinct
+ # to work with MySQL 5.7.6 which sets optimizer_switch='derived_merge=on'
+ subselect.from subsubselect.distinct.as('__active_record_temp')
+ end
+ end
+ end
+end
+
+module ActiveRecord
+ module ConnectionAdapters
+ class MysqlAdapter < AbstractMysqlAdapter
+ ADAPTER_NAME = 'MySQL'.freeze
+
+ # Get the client encoding for this database
+ def client_encoding
+ return @client_encoding if @client_encoding
+
+ result = exec_query(
+ "select @@character_set_client",
+ 'SCHEMA')
+ @client_encoding = ENCODINGS[result.rows.last.last]
+ end
+ end
+ end
+end
diff --git a/config/initializers/redis_config.rb b/config/initializers/redis_config.rb
new file mode 100644
index 0000000000..1cfecc2dd0
--- /dev/null
+++ b/config/initializers/redis_config.rb
@@ -0,0 +1,12 @@
+# This is a quick hack to get ExclusiveLease working in GitLab 8.5
+
+module Gitlab
+ REDIS_URL = begin
+ redis_config_file = Rails.root.join('config/resque.yml')
+ if File.exists?(redis_config_file)
+ YAML.load_file(redis_config_file)[Rails.env]
+ else
+ 'redis://localhost:6379'
+ end
+ end
+end
diff --git a/config/initializers/relative_url.rb.sample b/config/initializers/relative_url.rb.sample
new file mode 100644
index 0000000000..125297d538
--- /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 d0630b9fa0..e87899b2d5 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/initializers/smtp_settings.rb.sample b/config/initializers/smtp_settings.rb.sample
index ec182502d4..2287a76fca 100644
--- a/config/initializers/smtp_settings.rb.sample
+++ b/config/initializers/smtp_settings.rb.sample
@@ -12,7 +12,7 @@ if Rails.env.production?
ActionMailer::Base.smtp_settings = {
address: "email.server.com",
- port: 456,
+ port: 465,
user_name: "smtp",
password: "123456",
domain: "gitlab.company.com",
diff --git a/config/locales/en.yml b/config/locales/en.yml
index f6cfb5efd2..cedb5e207b 100644
--- a/config/locales/en.yml
+++ b/config/locales/en.yml
@@ -8,3 +8,7 @@ en:
wrong_size: "is the wrong size (should be %{file_size})"
size_too_small: "is too small (should be at least %{file_size})"
size_too_big: "is too big (should be at most %{file_size})"
+ views:
+ pagination:
+ previous: "Prev"
+ next: "Next"
diff --git a/config/routes.rb b/config/routes.rb
index 75418db8d2..cf2f379509 100644
--- a/config/routes.rb
+++ b/config/routes.rb
@@ -154,6 +154,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",
@@ -211,6 +216,8 @@ Rails.application.routes.draw do
end
resources :abuse_reports, only: [:index, :destroy]
+ resources :spam_logs, only: [:index, :destroy]
+
resources :applications
resources :groups, constraints: { id: /[^\/]+/ } do
@@ -225,7 +232,10 @@ Rails.application.routes.draw do
get :test
end
- resources :broadcast_messages, only: [:index, :edit, :create, :update, :destroy]
+ resources :broadcast_messages, only: [:index, :edit, :create, :update, :destroy] do
+ post :preview, on: :collection
+ end
+
resource :logs, only: [:show]
resource :background_jobs, controller: 'background_jobs', only: [:show]
@@ -246,6 +256,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
@@ -329,6 +347,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
@@ -347,6 +371,7 @@ Rails.application.routes.draw do
get :issues
get :merge_requests
get :projects
+ get :events
end
scope module: :groups do
@@ -490,12 +515,13 @@ Rails.application.routes.draw do
end
resource :avatar, only: [:show, :destroy]
- resources :commit, only: [:show], constraints: { id: /[[:alnum:]]{6,40}/ } do
+ resources :commit, only: [:show], constraints: { id: /\h{7,40}/ } do
member do
get :branches
get :builds
post :cancel_builds
post :retry_builds
+ post :revert
end
end
@@ -554,7 +580,7 @@ Rails.application.routes.draw do
end
end
- resource :fork, only: [:new, :create]
+ resources :forks, only: [:index, :new, :create]
resource :import, only: [:new, :create, :show]
resources :refs, only: [] do
@@ -602,7 +628,7 @@ Rails.application.routes.draw do
resource :variables, only: [:show, :update]
resources :triggers, only: [:index, :create, :destroy]
- resources :builds, only: [:index, :show] do
+ resources :builds, only: [:index, :show], constraints: { id: /\d+/ } do
collection do
post :cancel_all
end
@@ -611,6 +637,7 @@ Rails.application.routes.draw do
get :status
post :cancel
post :retry
+ post :erase
end
resource :artifacts, only: [] do
@@ -691,6 +718,12 @@ Rails.application.routes.draw do
end
resources :runner_projects, only: [:create, :destroy]
+ resources :badges, only: [], path: 'badges/*ref',
+ constraints: { ref: Gitlab::Regex.git_reference_regex } do
+ collection do
+ get :build, constraints: { format: /svg/ }
+ end
+ end
end
end
end
diff --git a/config/unicorn.rb.example b/config/unicorn.rb.example
index b937b09278..e5058cebce 100644
--- a/config/unicorn.rb.example
+++ b/config/unicorn.rb.example
@@ -10,9 +10,12 @@
# Note: If you change this file in a Merge Request, please also create a
# Merge Request on https://gitlab.com/gitlab-org/omnibus-gitlab/merge_requests
-#
-# WARNING: See config/application.rb under "Relative url support" for the list of
-# other files that need to be changed for relative url support
+
+# 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
#
# ENV['RAILS_RELATIVE_URL_ROOT'] = "/gitlab"
diff --git a/db/fixtures/development/14_builds.rb b/db/fixtures/development/14_builds.rb
index 03a1232384..e3ca2b4eea 100644
--- a/db/fixtures/development/14_builds.rb
+++ b/db/fixtures/development/14_builds.rb
@@ -1,24 +1,13 @@
class Gitlab::Seeder::Builds
- BUILD_STATUSES = %w(running pending success failed canceled)
-
def initialize(project)
@project = project
end
def seed!
ci_commits.each do |ci_commit|
- build = Ci::Build.new(build_attributes_for(ci_commit))
-
- artifacts_cache_file(artifacts_archive_path) do |file|
- build.artifacts_file = file
- end
-
- artifacts_cache_file(artifacts_metadata_path) do |file|
- build.artifacts_metadata = file
- end
-
begin
- build.save!
+ build_create!(ci_commit, name: 'test build 1')
+ build_create!(ci_commit, status: 'success', name: 'test build 2')
print '.'
rescue ActiveRecord::RecordInvalid
print 'F'
@@ -36,6 +25,28 @@ class Gitlab::Seeder::Builds
[]
end
+ def build_create!(ci_commit, opts = {})
+ attributes = build_attributes_for(ci_commit).merge(opts)
+ build = Ci::Build.new(attributes)
+
+ if %w(success failed).include?(build.status)
+ artifacts_cache_file(artifacts_archive_path) do |file|
+ build.artifacts_file = file
+ end
+
+ artifacts_cache_file(artifacts_metadata_path) do |file|
+ build.artifacts_metadata = file
+ end
+ end
+
+ build.save!
+
+ if %w(running success failed).include?(build.status)
+ # We need to set build trace after saving a build (id required)
+ build.trace = FFaker::Lorem.paragraphs(6).join("\n\n")
+ end
+ end
+
def build_attributes_for(ci_commit)
{ name: 'test build', commands: "$ build command",
stage: 'test', stage_idx: 1, ref: 'master',
@@ -49,7 +60,7 @@ class Gitlab::Seeder::Builds
end
def build_status
- BUILD_STATUSES.sample
+ Ci::Build::AVAILABLE_STATUSES.sample
end
def artifacts_archive_path
diff --git a/db/fixtures/production/001_admin.rb b/db/fixtures/production/001_admin.rb
index b0c0b6450f..308b0528c9 100644
--- a/db/fixtures/production/001_admin.rb
+++ b/db/fixtures/production/001_admin.rb
@@ -6,8 +6,10 @@ else
expire_time = nil
end
+email = ENV['GITLAB_ROOT_EMAIL'].presence || 'admin@example.com'
+
admin = User.create(
- email: "admin@example.com",
+ email: email,
name: "Administrator",
username: 'root',
password: password,
diff --git a/db/migrate/20151201203948_raise_hook_url_limit.rb b/db/migrate/20151201203948_raise_hook_url_limit.rb
new file mode 100644
index 0000000000..98a7fca6f6
--- /dev/null
+++ b/db/migrate/20151201203948_raise_hook_url_limit.rb
@@ -0,0 +1,5 @@
+class RaiseHookUrlLimit < ActiveRecord::Migration
+ def change
+ change_column :web_hooks, :url, :string, limit: 2000
+ end
+end
diff --git a/db/migrate/20151231152326_add_akismet_to_application_settings.rb b/db/migrate/20151231152326_add_akismet_to_application_settings.rb
new file mode 100644
index 0000000000..3f52c758f9
--- /dev/null
+++ b/db/migrate/20151231152326_add_akismet_to_application_settings.rb
@@ -0,0 +1,8 @@
+class AddAkismetToApplicationSettings < ActiveRecord::Migration
+ def change
+ change_table :application_settings do |t|
+ t.boolean :akismet_enabled, default: false
+ t.string :akismet_api_key
+ end
+ end
+end
diff --git a/db/migrate/20160109054846_create_spam_logs.rb b/db/migrate/20160109054846_create_spam_logs.rb
new file mode 100644
index 0000000000..f12fe9f8f7
--- /dev/null
+++ b/db/migrate/20160109054846_create_spam_logs.rb
@@ -0,0 +1,16 @@
+class CreateSpamLogs < ActiveRecord::Migration
+ def change
+ create_table :spam_logs do |t|
+ t.integer :user_id
+ t.string :source_ip
+ t.string :user_agent
+ t.boolean :via_api
+ t.integer :project_id
+ t.string :noteable_type
+ t.string :title
+ t.text :description
+
+ t.timestamps null: false
+ end
+ end
+end
diff --git a/db/migrate/20160121030729_add_email_author_in_body_to_application_settings.rb b/db/migrate/20160121030729_add_email_author_in_body_to_application_settings.rb
new file mode 100644
index 0000000000..d50791410f
--- /dev/null
+++ b/db/migrate/20160121030729_add_email_author_in_body_to_application_settings.rb
@@ -0,0 +1,5 @@
+class AddEmailAuthorInBodyToApplicationSettings < ActiveRecord::Migration
+ def change
+ add_column :application_settings, :email_author_in_body, :boolean, default: false
+ end
+end
diff --git a/db/migrate/20160122185421_add_pending_delete_to_project.rb b/db/migrate/20160122185421_add_pending_delete_to_project.rb
new file mode 100644
index 0000000000..046a5d8fc3
--- /dev/null
+++ b/db/migrate/20160122185421_add_pending_delete_to_project.rb
@@ -0,0 +1,5 @@
+class AddPendingDeleteToProject < ActiveRecord::Migration
+ def change
+ add_column :projects, :pending_delete, :boolean, default: false
+ end
+end
diff --git a/db/migrate/20160128212447_remove_ip_blocking_settings_from_application_settings.rb b/db/migrate/20160128212447_remove_ip_blocking_settings_from_application_settings.rb
new file mode 100644
index 0000000000..41821cdcc4
--- /dev/null
+++ b/db/migrate/20160128212447_remove_ip_blocking_settings_from_application_settings.rb
@@ -0,0 +1,6 @@
+class RemoveIpBlockingSettingsFromApplicationSettings < ActiveRecord::Migration
+ def change
+ remove_column :application_settings, :ip_blocking_enabled, :boolean, default: false
+ remove_column :application_settings, :dnsbl_servers_list, :text
+ end
+end
diff --git a/db/migrate/20160129135155_remove_dot_atom_path_ending_of_projects.rb b/db/migrate/20160129135155_remove_dot_atom_path_ending_of_projects.rb
new file mode 100644
index 0000000000..d3ea956952
--- /dev/null
+++ b/db/migrate/20160129135155_remove_dot_atom_path_ending_of_projects.rb
@@ -0,0 +1,80 @@
+class RemoveDotAtomPathEndingOfProjects < ActiveRecord::Migration
+ include Gitlab::ShellAdapter
+
+ class ProjectPath
+ attr_reader :old_path, :id, :namespace_path
+
+ def initialize(old_path, id, namespace_path, namespace_id)
+ @old_path = old_path
+ @id = id
+ @namespace_path = namespace_path
+ @namespace_id = namespace_id
+ end
+
+ def clean_path
+ @_clean_path ||= PathCleaner.clean(@old_path, @namespace_id)
+ end
+ end
+
+ class PathCleaner
+ def initialize(path, namespace_id)
+ @namespace_id = namespace_id
+ @path = path
+ end
+
+ def self.clean(*args)
+ new(*args).clean
+ end
+
+ def clean
+ path = cleaned_path
+ count = 0
+ while path_exists?(path)
+ path = "#{cleaned_path}#{count}"
+ count += 1
+ end
+ path
+ end
+
+ private
+
+ def cleaned_path
+ @_cleaned_path ||= @path.gsub(/\.atom\z/, '-atom')
+ end
+
+ def path_exists?(path)
+ Project.find_by_path_and_namespace_id(path, @namespace_id)
+ end
+ end
+
+ def projects_with_dot_atom
+ select_all("SELECT p.id, p.path, n.path as namespace_path, n.id as namespace_id FROM projects p inner join namespaces n on n.id = p.namespace_id WHERE p.path LIKE '%.atom'")
+ end
+
+ def up
+ projects_with_dot_atom.each do |project|
+ project_path = ProjectPath.new(project['path'], project['id'], project['namespace_path'], project['namespace_id'])
+ clean_path(project_path) if rename_project_repo(project_path)
+ end
+ end
+
+ private
+
+ def clean_path(project_path)
+ execute "UPDATE projects SET path = #{sanitize(project_path.clean_path)} WHERE id = #{project_path.id}"
+ end
+
+ def rename_project_repo(project_path)
+ old_path_with_namespace = File.join(project_path.namespace_path, project_path.old_path)
+ new_path_with_namespace = File.join(project_path.namespace_path, project_path.clean_path)
+
+ gitlab_shell.mv_repository("#{old_path_with_namespace}.wiki", "#{new_path_with_namespace}.wiki")
+ gitlab_shell.mv_repository(old_path_with_namespace, new_path_with_namespace)
+ rescue
+ false
+ end
+
+ def sanitize(value)
+ ActiveRecord::Base.connection.quote(value)
+ end
+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 0000000000..f0d9422651
--- /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 0000000000..f9912f2274
--- /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/20160202164642_add_allow_guest_to_access_builds_project.rb b/db/migrate/20160202164642_add_allow_guest_to_access_builds_project.rb
new file mode 100644
index 0000000000..793984343b
--- /dev/null
+++ b/db/migrate/20160202164642_add_allow_guest_to_access_builds_project.rb
@@ -0,0 +1,5 @@
+class AddAllowGuestToAccessBuildsProject < ActiveRecord::Migration
+ def change
+ add_column :projects, :public_builds, :boolean, default: true, null: false
+ end
+end
diff --git a/db/migrate/20160209130428_add_index_to_snippet.rb b/db/migrate/20160209130428_add_index_to_snippet.rb
new file mode 100644
index 0000000000..95d5719be5
--- /dev/null
+++ b/db/migrate/20160209130428_add_index_to_snippet.rb
@@ -0,0 +1,5 @@
+class AddIndexToSnippet < ActiveRecord::Migration
+ def change
+ add_index :snippets, :updated_at
+ end
+end
diff --git a/db/migrate/20160212123307_create_tasks.rb b/db/migrate/20160212123307_create_tasks.rb
new file mode 100644
index 0000000000..c3f6f3abc2
--- /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 0000000000..eed6d1f236
--- /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 0000000000..da5cb2e05d
--- /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 0000000000..30c10d2714
--- /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 0000000000..bec66bcc71
--- /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/migrate/20160309140734_fix_todos.rb b/db/migrate/20160309140734_fix_todos.rb
new file mode 100644
index 0000000000..ebe0fc8230
--- /dev/null
+++ b/db/migrate/20160309140734_fix_todos.rb
@@ -0,0 +1,16 @@
+class FixTodos < ActiveRecord::Migration
+ def up
+ execute <<-SQL
+ DELETE FROM todos
+ WHERE todos.target_type IN ('Commit', 'ProjectSnippet')
+ OR NOT EXISTS (
+ SELECT *
+ FROM projects
+ WHERE projects.id = todos.project_id
+ )
+ SQL
+ end
+
+ def down
+ end
+end
diff --git a/db/schema.rb b/db/schema.rb
index 258366fcd0..3c839db8db 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: 20160128233227) do
+ActiveRecord::Schema.define(version: 20160309140734) 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: 20160128233227) 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"
@@ -64,8 +73,9 @@ ActiveRecord::Schema.define(version: 20160128233227) do
t.integer "metrics_sample_interval", default: 15
t.boolean "sentry_enabled", default: false
t.string "sentry_dsn"
- t.boolean "ip_blocking_enabled", default: false
- t.text "dnsbl_servers_list"
+ t.boolean "akismet_enabled", default: false
+ t.string "akismet_api_key"
+ t.boolean "email_author_in_body", default: false
end
create_table "audit_events", force: :cascade do |t|
@@ -128,6 +138,8 @@ ActiveRecord::Schema.define(version: 20160128233227) 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
@@ -135,6 +147,7 @@ ActiveRecord::Schema.define(version: 20160128233227) 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
@@ -441,7 +454,8 @@ ActiveRecord::Schema.define(version: 20160128233227) 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
@@ -521,6 +535,7 @@ ActiveRecord::Schema.define(version: 20160128233227) 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
@@ -679,6 +694,8 @@ ActiveRecord::Schema.define(version: 20160128233227) do
t.string "build_coverage_regex"
t.boolean "build_allow_git_fetch", default: true, null: false
t.integer "build_timeout", default: 3600, null: false
+ t.boolean "pending_delete", default: false
+ t.boolean "public_builds", default: true, null: false
end
add_index "projects", ["builds_enabled", "shared_runners_enabled"], name: "index_projects_on_builds_enabled_and_shared_runners_enabled", using: :btree
@@ -769,8 +786,22 @@ ActiveRecord::Schema.define(version: 20160128233227) do
add_index "snippets", ["created_at"], name: "index_snippets_on_created_at", using: :btree
add_index "snippets", ["expires_at"], name: "index_snippets_on_expires_at", using: :btree
add_index "snippets", ["project_id"], name: "index_snippets_on_project_id", using: :btree
+ add_index "snippets", ["updated_at"], name: "index_snippets_on_updated_at", using: :btree
add_index "snippets", ["visibility_level"], name: "index_snippets_on_visibility_level", using: :btree
+ create_table "spam_logs", force: :cascade do |t|
+ t.integer "user_id"
+ t.string "source_ip"
+ t.string "user_agent"
+ t.boolean "via_api"
+ t.integer "project_id"
+ t.string "noteable_type"
+ t.string "title"
+ t.text "description"
+ t.datetime "created_at", null: false
+ t.datetime "updated_at", null: false
+ end
+
create_table "subscriptions", force: :cascade do |t|
t.integer "user_id"
t.integer "subscribable_id"
@@ -802,6 +833,26 @@ ActiveRecord::Schema.define(version: 20160128233227) 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
@@ -884,19 +935,19 @@ ActiveRecord::Schema.define(version: 20160128233227) do
add_index "users_star_projects", ["user_id"], name: "index_users_star_projects_on_user_id", using: :btree
create_table "web_hooks", force: :cascade do |t|
- t.string "url"
+ t.string "url", limit: 2000
t.integer "project_id"
t.datetime "created_at"
t.datetime "updated_at"
- t.string "type", default: "ProjectHook"
+ t.string "type", default: "ProjectHook"
t.integer "service_id"
- t.boolean "push_events", default: true, null: false
- t.boolean "issues_events", default: false, null: false
- t.boolean "merge_requests_events", default: false, null: false
- t.boolean "tag_push_events", default: false
- t.boolean "note_events", default: false, null: false
- t.boolean "enable_ssl_verification", default: true
- t.boolean "build_events", default: false, null: false
+ t.boolean "push_events", default: true, null: false
+ t.boolean "issues_events", default: false, null: false
+ t.boolean "merge_requests_events", default: false, null: false
+ t.boolean "tag_push_events", default: false
+ t.boolean "note_events", default: false, null: false
+ t.boolean "enable_ssl_verification", default: true
+ t.boolean "build_events", default: false, null: false
end
add_index "web_hooks", ["created_at", "id"], name: "index_web_hooks_on_created_at_and_id", using: :btree
diff --git a/doc/administration/environment_variables.md b/doc/administration/environment_variables.md
index 42a27dcf6d..43ab153d76 100644
--- a/doc/administration/environment_variables.md
+++ b/doc/administration/environment_variables.md
@@ -1,55 +1,61 @@
# Environment Variables
-## Introduction
+GitLab exposes certain environment variables which can be used to override
+their defaults values.
-Commonly people configure GitLab via the gitlab.rb configuration file in the Omnibus package.
+People usually configure GitLab via `/etc/gitlab/gitlab.rb` for Omnibus
+installations, or `gitlab.yml` for installations from source.
-But if you prefer to use environment variables we allow that too.
+Below you will find the supported environment variables which you can use to
+override certain values.
## Supported environment variables
-Variable | Type | Explanation
+Variable | Type | Description
-------- | ---- | -----------
-GITLAB_ROOT_PASSWORD | string | sets the password for the `root` user on installation
-GITLAB_HOST | url | hostname of the GitLab server includes http or https
-RAILS_ENV | production / development / staging / test | Rails environment
-DATABASE_URL | url | For example: postgresql://localhost/blog_development?pool=5
-GITLAB_EMAIL_FROM | email | Email address used in the "From" field in mails sent by GitLab
-GITLAB_EMAIL_DISPLAY_NAME | string | Name used in the "From" field in mails sent by GitLab
-GITLAB_EMAIL_REPLY_TO | email | Email address used in the "Reply-To" field in mails sent by GitLab
-GITLAB_UNICORN_MEMORY_MIN | integer | The minimum memory threshold (in bytes) for the Unicorn worker killer
-GITLAB_UNICORN_MEMORY_MAX | integer | The maximum memory threshold (in bytes) for the Unicorn worker killer
+`GITLAB_ROOT_PASSWORD` | string | Sets the password for the `root` user on installation
+`GITLAB_HOST` | string | The full URL of the GitLab server (including `http://` or `https://`)
+`RAILS_ENV` | string | The Rails environment; can be one of `production`, `development`, `staging` or `test`
+`DATABASE_URL` | string | The database URL; is of the form: `postgresql://localhost/blog_development`
+`GITLAB_EMAIL_FROM` | string | The e-mail address used in the "From" field in e-mails sent by GitLab
+`GITLAB_EMAIL_DISPLAY_NAME` | string | The name used in the "From" field in e-mails sent by GitLab
+`GITLAB_EMAIL_REPLY_TO` | string | The e-mail address used in the "Reply-To" field in e-mails sent by GitLab
+`GITLAB_UNICORN_MEMORY_MIN` | integer | The minimum memory threshold (in bytes) for the Unicorn worker killer
+`GITLAB_UNICORN_MEMORY_MAX` | integer | The maximum memory threshold (in bytes) for the Unicorn worker killer
## Complete database variables
-As explained in the [Heroku documentation](https://devcenter.heroku.com/articles/rails-database-connection-behavior) the DATABASE_URL doesn't let you set:
+The recommended way of specifying your database connection information is to set
+the `DATABASE_URL` environment variable. This variable only holds connection
+information (`adapter`, `database`, `username`, `password`, `host` and `port`),
+but not behavior information (`encoding`, `pool`). If you don't want to use
+`DATABASE_URL` and/or want to set database behavior information, you will have
+to either:
-- adapter
-- database
-- username
-- password
-- host
-- port
+- copy our template file: `cp config/database.yml.env config/database.yml`, or
+- set a value for some `GITLAB_DATABASE_XXX` variables
-To do so please `cp config/database.yml.env config/database.yml` and use the following variables:
+The list of `GITLAB_DATABASE_XXX` variables that you can set is:
-Variable | Default
---- | ---
-GITLAB_DATABASE_ADAPTER | postgresql
-GITLAB_DATABASE_ENCODING | unicode
-GITLAB_DATABASE_DATABASE | gitlab_#{ENV['RAILS_ENV']
-GITLAB_DATABASE_POOL | 10
-GITLAB_DATABASE_USERNAME | root
-GITLAB_DATABASE_PASSWORD |
-GITLAB_DATABASE_HOST | localhost
-GITLAB_DATABASE_PORT | 5432
+Variable | Default value | Overridden by `DATABASE_URL`?
+-------- | ------------- | -----------------------------
+`GITLAB_DATABASE_ADAPTER` | `postgresql` (for MySQL use `mysql2`) | Yes
+`GITLAB_DATABASE_DATABASE` | `gitlab_#{ENV['RAILS_ENV']` | Yes
+`GITLAB_DATABASE_USERNAME` | `root` | Yes
+`GITLAB_DATABASE_PASSWORD` | None | Yes
+`GITLAB_DATABASE_HOST` | `localhost` | Yes
+`GITLAB_DATABASE_PORT` | `5432` | Yes
+`GITLAB_DATABASE_ENCODING` | `unicode` | No
+`GITLAB_DATABASE_POOL` | `10` | No
## Adding more variables
We welcome merge requests to make more settings configurable via variables.
-Please stick to the naming scheme "GITLAB_#{name 1_settings.rb in upper case}".
+Please make changes in the `config/initializers/1_settings.rb` file and stick
+to the naming scheme `GITLAB_#{name in 1_settings.rb in upper case}`.
## Omnibus configuration
-It's possible to preconfigure the GitLab image by adding the environment variable: `GITLAB_OMNIBUS_CONFIG` to docker run command.
+It's possible to preconfigure the GitLab docker image by adding the environment
+variable `GITLAB_OMNIBUS_CONFIG` to the `docker run` command.
For more information see the ['preconfigure-docker-container' section in the Omnibus documentation](http://doc.gitlab.com/omnibus/docker/#preconfigure-docker-container).
diff --git a/doc/administration/housekeeping.md b/doc/administration/housekeeping.md
index c27cf1812d..a5fa7d358a 100644
--- a/doc/administration/housekeeping.md
+++ b/doc/administration/housekeeping.md
@@ -4,8 +4,8 @@ _**Note:** This feature was [introduced][ce-2371] in GitLab 8.4_
---
-The housekeeping function runs [`git gc`][man] on the current project Git
-repository.
+The housekeeping function runs `git gc` ([man page][man]) on the current
+project Git repository.
`git gc` runs a number of housekeeping tasks, such as compressing file
revisions (to reduce disk space and increase performance) and removing
diff --git a/doc/api/README.md b/doc/api/README.md
index 2fa177ff4d..7629ef294a 100644
--- a/doc/api/README.md
+++ b/doc/api/README.md
@@ -1,7 +1,13 @@
# GitLab API
+Automate GitLab via a simple and powerful API. All definitions can be found
+under [`/lib/api`](https://gitlab.com/gitlab-org/gitlab-ce/tree/master/lib/api).
+
## Resources
+Documentation for various API resources can be found separately in the
+following locations:
+
- [Users](users.md)
- [Session](session.md)
- [Projects](projects.md) including setting Webhooks
@@ -26,17 +32,17 @@
- [Builds](builds.md)
- [Build triggers](build_triggers.md)
- [Build Variables](build_variables.md)
+- [Runners](runners.md)
-## Clients
+## Authentication
-Find API Clients for GitLab [on our website](https://about.gitlab.com/applications/#api-clients).
-You can use [GitLab as an OAuth2 client](oauth2.md) to make API calls.
+All API requests require authentication. You need to pass a `private_token`
+parameter via query string or header. If passed as a header, the header name
+must be `PRIVATE-TOKEN` (uppercase and with a dash instead of an underscore).
+You can find or reset your private token in your account page (`/profile/account`).
-## Introduction
-
-All API requests require authentication. You need to pass a `private_token` parameter by URL or header. If passed as header, the header name must be "PRIVATE-TOKEN" (capital and with dash instead of underscore). You can find or reset your private token in your profile.
-
-If no, or an invalid, `private_token` is provided then an error message will be returned with status code 401:
+If `private_token` is invalid or omitted, then an error message will be
+returned with status code `401`:
```json
{
@@ -44,71 +50,83 @@ If no, or an invalid, `private_token` is provided then an error message will be
}
```
-API requests should be prefixed with `api` and the API version. The API version is defined in `lib/api.rb`.
+API requests should be prefixed with `api` and the API version. The API version
+is defined in [`lib/api.rb`][lib-api-url].
Example of a valid API request:
-```
-GET http://example.com/api/v3/projects?private_token=QVy1PB7sTxfy4pqfZM1U
+```shell
+GET https://gitlab.example.com/api/v3/projects?private_token=9koXpg98eAheJpvBs5tK
```
-Example for a valid API request using curl and authentication via header:
+Example of a valid API request using cURL and authentication via header:
-```
-curl --header "PRIVATE-TOKEN: QVy1PB7sTxfy4pqfZM1U" "http://example.com/api/v3/projects"
+```shell
+curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v3/projects"
```
-The API uses JSON to serialize data. You don't need to specify `.json` at the end of API URL.
+The API uses JSON to serialize data. You don't need to specify `.json` at the
+end of an API URL.
## Authentication with OAuth2 token
-Instead of the private_token you can transmit the OAuth2 access token as a header or as a parameter.
+Instead of the `private_token` you can transmit the OAuth2 access token as a
+header or as a parameter.
-### OAuth2 token (as a parameter)
+Example of OAuth2 token as a parameter:
-```
-curl https://localhost:3000/api/v3/user?access_token=OAUTH-TOKEN
+```shell
+curl https://gitlab.example.com/api/v3/user?access_token=OAUTH-TOKEN
```
-### OAuth2 token (as a header)
+Example of OAuth2 token as a header:
-```
-curl -H "Authorization: Bearer OAUTH-TOKEN" https://localhost:3000/api/v3/user
+```shell
+curl -H "Authorization: Bearer OAUTH-TOKEN" https://example.com/api/v3/user
```
Read more about [GitLab as an OAuth2 client](oauth2.md).
## Status codes
-The API is designed to return different status codes according to context and action. In this way if a request results in an error the caller is able to get insight into what went wrong, e.g. status code `400 Bad Request` is returned if a required attribute is missing from the request. The following list gives an overview of how the API functions generally behave.
+The API is designed to return different status codes according to context and
+action. This way, if a request results in an error, the caller is able to get
+insight into what went wrong.
-API request types:
+The following table gives an overview of how the API functions generally behave.
-- `GET` requests access one or more resources and return the result as JSON
-- `POST` requests return `201 Created` if the resource is successfully created and return the newly created resource as JSON
-- `GET`, `PUT` and `DELETE` return `200 OK` if the resource is accessed, modified or deleted successfully, the (modified) result is returned as JSON
-- `DELETE` requests are designed to be idempotent, meaning a request a resource still returns `200 OK` even it was deleted before or is not available. The reasoning behind it is the user is not really interested if the resource existed before or not.
+| Request type | Description |
+| ------------ | ----------- |
+| `GET` | Access one or more resources and return the result as JSON. |
+| `POST` | Return `201 Created` if the resource is successfully created and return the newly created resource as JSON. |
+| `GET` / `PUT` / `DELETE` | Return `200 OK` if the resource is accessed, modified or deleted successfully. The (modified) result is returned as JSON. |
+| `DELETE` | Designed to be idempotent, meaning a request to a resource still returns `200 OK` even it was deleted before or is not available. The reasoning behind this, is that the user is not really interested if the resource existed before or not. |
-The following list shows the possible return codes for API requests.
+The following table shows the possible return codes for API requests.
-Return values:
-
-- `200 OK` - The `GET`, `PUT` or `DELETE` request was successful, the resource(s) itself is returned as JSON
-- `201 Created` - The `POST` request was successful and the resource is returned as JSON
-- `400 Bad Request` - A required attribute of the API request is missing, e.g. the title of an issue is not given
-- `401 Unauthorized` - The user is not authenticated, a valid user token is necessary, see above
-- `403 Forbidden` - The request is not allowed, e.g. the user is not allowed to delete a project
-- `404 Not Found` - A resource could not be accessed, e.g. an ID for a resource could not be found
-- `405 Method Not Allowed` - The request is not supported
-- `409 Conflict` - A conflicting resource already exists, e.g. creating a project with a name that already exists
-- `422 Unprocessable` - The entity could not be processed
-- `500 Server Error` - While handling the request something went wrong on the server side
+| Return values | Description |
+| ------------- | ----------- |
+| `200 OK` | The `GET`, `PUT` or `DELETE` request was successful, the resource(s) itself is returned as JSON. |
+| `201 Created` | The `POST` request was successful and the resource is returned as JSON. |
+| `400 Bad Request` | A required attribute of the API request is missing, e.g., the title of an issue is not given. |
+| `401 Unauthorized` | The user is not authenticated, a valid [user token](#authentication) is necessary. |
+| `403 Forbidden` | The request is not allowed, e.g., the user is not allowed to delete a project. |
+| `404 Not Found` | A resource could not be accessed, e.g., an ID for a resource could not be found. |
+| `405 Method Not Allowed` | The request is not supported. |
+| `409 Conflict` | A conflicting resource already exists, e.g., creating a project with a name that already exists. |
+| `422 Unprocessable` | The entity could not be processed. |
+| `500 Server Error` | While handling the request something went wrong server-side. |
## Sudo
-All API requests support performing an api call as if you were another user, if your private token is for an administration account. You need to pass `sudo` parameter by URL or header with an id or username of the user you want to perform the operation as. If passed as header, the header name must be "SUDO" (capitals).
+All API requests support performing an API call as if you were another user,
+provided your private token is from an administrator account. You need to pass
+the `sudo` parameter either via query string or a header with an ID/username of
+the user you want to perform the operation as. If passed as a header, the
+header name must be `SUDO` (uppercase).
-If a non administrative `private_token` is provided then an error message will be returned with status code 403:
+If a non administrative `private_token` is provided, then an error message will
+be returned with status code `403`:
```json
{
@@ -116,7 +134,8 @@ If a non administrative `private_token` is provided then an error message will b
}
```
-If the sudo user id or username cannot be found then an error message will be returned with status code 404:
+If the sudo user ID or username cannot be found, an error message will be
+returned with status code `404`:
```json
{
@@ -124,94 +143,181 @@ If the sudo user id or username cannot be found then an error message will be re
}
```
-Example of a valid API with sudo request:
+---
-```
-GET http://example.com/api/v3/projects?private_token=QVy1PB7sTxfy4pqfZM1U&sudo=username
+Example of a valid API call and a request using cURL with sudo request,
+providing a username:
+
+```shell
+GET /projects?private_token=9koXpg98eAheJpvBs5tK&sudo=username
```
-```
-GET http://example.com/api/v3/projects?private_token=QVy1PB7sTxfy4pqfZM1U&sudo=23
+```shell
+curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" --header "SUDO: username" "https://gitlab.example.com/api/v3/projects"
```
-Example for a valid API request with sudo using curl and authentication via header:
+Example of a valid API call and a request using cURL with sudo request,
+providing an ID:
-```
-curl --header "PRIVATE-TOKEN: QVy1PB7sTxfy4pqfZM1U" --header "SUDO: username" "http://example.com/api/v3/projects"
+```shell
+GET /projects?private_token=9koXpg98eAheJpvBs5tK&sudo=23
```
-```
-curl --header "PRIVATE-TOKEN: QVy1PB7sTxfy4pqfZM1U" --header "SUDO: 23" "http://example.com/api/v3/projects"
+```shell
+curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" --header "SUDO: 23" "https://gitlab.example.com/api/v3/projects"
```
## Pagination
-When listing resources you can pass the following parameters:
+Sometimes the returned result will span across many pages. When listing
+resources you can pass the following parameters:
-- `page` (default: `1`) - page number
-- `per_page` (default: `20`, max: `100`) - number of items to list per page
+| Parameter | Description |
+| --------- | ----------- |
+| `page` | Page number (default: `1`) |
+| `per_page`| Number of items to list per page (default: `20`, max: `100`) |
-[Link headers](http://www.w3.org/wiki/LinkHeader) are send back with each response. These have `rel` prev/next/first/last and contain the relevant URL. Please use these instead of generating your own URLs.
+In the example below, we list 50 [namespaces](namespaces.md) per page.
-## id vs iid
+```bash
+curl -X PUT -H "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v3/namespaces?per_page=50
+```
-When you work with API you may notice two similar fields in api entities: id and iid. The main difference between them is scope. Example:
+### Pagination Link header
-Issue:
+[Link headers](http://www.w3.org/wiki/LinkHeader) are sent back with each
+response. They have `rel` set to prev/next/first/last and contain the relevant
+URL. Please use these links instead of generating your own URLs.
- id: 46
- iid: 5
+In the cURL example below, we limit the output to 3 items per page (`per_page=3`)
+and we request the second page (`page=2`) of [comments](notes.md) of the issue
+with ID `8` which belongs to the project with ID `8`:
-- id - is unique across all issues. It's used for any api call.
-- iid - is unique only in scope of a single project. When you browse issues or merge requests with Web UI, you see iid.
+```bash
+curl -I -H "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v3/projects/8/issues/8/notes?per_page=3&page=2
+```
-So if you want to get issue with api you use `http://host/api/v3/.../issues/:id.json`. But when you want to create a link to web page - use `http:://host/project/issues/:iid.json`
+The response will then be:
+
+```
+HTTP/1.1 200 OK
+Cache-Control: no-cache
+Content-Length: 1103
+Content-Type: application/json
+Date: Mon, 18 Jan 2016 09:43:18 GMT
+Link: ; rel="prev", ; rel="next", ; rel="first", ; rel="last"
+Status: 200 OK
+Vary: Origin
+X-Next-Page: 3
+X-Page: 2
+X-Per-Page: 3
+X-Prev-Page: 1
+X-Request-Id: 732ad4ee-9870-4866-a199-a9db0cde3c86
+X-Runtime: 0.108688
+X-Total: 8
+X-Total-Pages: 3
+```
+
+### Other pagination headers
+
+Additional pagination headers are also sent back.
+
+| Header | Description |
+| ------ | ----------- |
+| `X-Total` | The total number of items |
+| `X-Total-Pages` | The total number of pages |
+| `X-Per-Page` | The number of items per page |
+| `X-Page` | The index of the current page (starting at 1) |
+| `X-Next-Page` | The index of the next page |
+| `X-Prev-Page` | The index of the previous page |
+
+## `id` vs `iid`
+
+When you work with the API, you may notice two similar fields in API entities:
+`id` and `iid`. The main difference between them is scope.
+
+For example, an issue might have `id: 46` and `iid: 5`.
+
+| Parameter | Description |
+| --------- | ----------- |
+| `id` | Is unique across all issues and is used for any API call |
+| `iid` | Is unique only in scope of a single project. When you browse issues or merge requests with the Web UI, you see the `iid` |
+
+That means that if you want to get an issue via the API you should use the `id`:
+
+```bash
+GET /projects/42/issues/:id
+```
+
+On the other hand, if you want to create a link to a web page you should use
+the `iid`:
+
+```bash
+GET /projects/42/issues/:iid
+```
## Data validation and error reporting
-When working with the API you may encounter validation errors. In such case, the API will answer with an HTTP `400` status.
+When working with the API you may encounter validation errors, in which case
+the API will answer with an HTTP `400` status.
+
Such errors appear in two cases:
-* A required attribute of the API request is missing, e.g. the title of an issue is not given
-* An attribute did not pass the validation, e.g. user bio is too long
+- A required attribute of the API request is missing, e.g., the title of an
+ issue is not given
+- An attribute did not pass the validation, e.g., user bio is too long
When an attribute is missing, you will get something like:
- HTTP/1.1 400 Bad Request
- Content-Type: application/json
+```
+HTTP/1.1 400 Bad Request
+Content-Type: application/json
+{
+ "message":"400 (Bad request) \"title\" not given"
+}
+```
- {
- "message":"400 (Bad request) \"title\" not given"
+When a validation error occurs, error messages will be different. They will
+hold all details of validation errors:
+
+```
+HTTP/1.1 400 Bad Request
+Content-Type: application/json
+{
+ "message": {
+ "bio": [
+ "is too long (maximum is 255 characters)"
+ ]
}
+}
+```
-When a validation error occurs, error messages will be different. They will hold all details of validation errors:
+This makes error messages more machine-readable. The format can be described as
+follows:
- HTTP/1.1 400 Bad Request
- Content-Type: application/json
-
- {
- "message": {
- "bio": [
- "is too long (maximum is 255 characters)"
- ]
- }
- }
-
-This makes error messages more machine-readable. The format can be described as follow:
-
- {
- "message": {
+```json
+{
+ "message": {
+ "": [
+ "",
+ "",
+ ...
+ ],
+ "": {
"": [
"",
"",
...
],
- "": {
- "": [
- "",
- "",
- ...
- ],
- }
}
}
+}
+```
+
+## Clients
+
+There are many unofficial GitLab API Clients for most of the popular
+programming languages. Visit the [GitLab website] for a complete list.
+
+[GitLab website]: https://about.gitlab.com/applications/#api-clients "Clients using the GitLab API"
+[lib-api-url]: https://gitlab.com/gitlab-org/gitlab-ce/tree/master/lib/api/api.rb
diff --git a/doc/api/branches.md b/doc/api/branches.md
index 6a9c10c852..abc4732c39 100644
--- a/doc/api/branches.md
+++ b/doc/api/branches.md
@@ -8,13 +8,21 @@ Get a list of repository branches from a project, sorted by name alphabetically.
GET /projects/:id/repository/branches
```
-Parameters:
+| Attribute | Type | Required | Description |
+| --------- | ---- | -------- | ----------- |
+| `id` | integer | yes | The ID of a project |
-- `id` (required) - The ID of a project
+```bash
+curl -H "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v3/projects/5/repository/branches
+```
+
+Example response:
```json
[
{
+ "name": "master",
+ "protected": true,
"commit": {
"author_email": "john@example.com",
"author_name": "John Smith",
@@ -27,10 +35,9 @@ Parameters:
"parent_ids": [
"4ad91d3c1144c406e50c7b33bae684bd6837faf8"
]
- },
- "name": "master",
- "protected": true
- }
+ }
+ },
+ ...
]
```
@@ -42,13 +49,21 @@ Get a single project repository branch.
GET /projects/:id/repository/branches/:branch
```
-Parameters:
+| Attribute | Type | Required | Description |
+| --------- | ---- | -------- | ----------- |
+| `id` | integer | yes | The ID of a project |
+| `branch` | string | yes | The name of the branch |
-- `id` (required) - The ID of a project
-- `branch` (required) - The name of the branch
+```bash
+curl -H "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v3/projects/5/repository/branches/master
+```
+
+Example response:
```json
{
+ "name": "master",
+ "protected": true,
"commit": {
"author_email": "john@example.com",
"author_name": "John Smith",
@@ -61,25 +76,30 @@ Parameters:
"parent_ids": [
"4ad91d3c1144c406e50c7b33bae684bd6837faf8"
]
- },
- "name": "master",
- "protected": true
+ }
}
```
## Protect repository branch
-Protects a single project repository branch. This is an idempotent function, protecting an already
-protected repository branch still returns a `200 OK` status code.
+Protects a single project repository branch. This is an idempotent function,
+protecting an already protected repository branch still returns a `200 OK`
+status code.
```
PUT /projects/:id/repository/branches/:branch/protect
```
-Parameters:
+```bash
+curl -X PUT -H "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v3/projects/5/repository/branches/master/protect
+```
-- `id` (required) - The ID of a project
-- `branch` (required) - The name of the branch
+| Attribute | Type | Required | Description |
+| --------- | ---- | -------- | ----------- |
+| `id` | integer | yes | The ID of a project |
+| `branch` | string | yes | The name of the branch |
+
+Example response:
```json
{
@@ -103,17 +123,24 @@ Parameters:
## Unprotect repository branch
-Unprotects a single project repository branch. This is an idempotent function, unprotecting an already
-unprotected repository branch still returns a `200 OK` status code.
+Unprotects a single project repository branch. This is an idempotent function,
+unprotecting an already unprotected repository branch still returns a `200 OK`
+status code.
```
PUT /projects/:id/repository/branches/:branch/unprotect
```
-Parameters:
+```bash
+curl -X PUT -H "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v3/projects/5/repository/branches/master/unprotect
+```
-- `id` (required) - The ID of a project
-- `branch` (required) - The name of the branch
+| Attribute | Type | Required | Description |
+| --------- | ---- | -------- | ----------- |
+| `id` | integer | yes | The ID of a project |
+| `branch` | string | yes | The name of the branch |
+
+Example response:
```json
{
@@ -141,11 +168,17 @@ Parameters:
POST /projects/:id/repository/branches
```
-Parameters:
+| Attribute | Type | Required | Description |
+| --------- | ---- | -------- | ----------- |
+| `id` | integer | yes | The ID of a project |
+| `branch_name` | string | yes | The name of the branch |
+| `ref` | string | yes | The branch name or commit SHA to create branch from |
-- `id` (required) - The ID of a project
-- `branch_name` (required) - The name of the branch
-- `ref` (required) - Create branch from commit SHA or existing branch
+```bash
+curl -X POST -H "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v3/projects/5/repository/branches?branch_name=newbranch&ref=master"
+```
+
+Example response:
```json
{
@@ -162,12 +195,13 @@ Parameters:
"4ad91d3c1144c406e50c7b33bae684bd6837faf8"
]
},
- "name": "master",
+ "name": "newbranch",
"protected": false
}
```
-It return 200 if succeed or 400 if failed with error message explaining reason.
+It returns `200` if it succeeds or `400` if failed with an error message
+explaining the reason.
## Delete repository branch
@@ -175,18 +209,22 @@ It return 200 if succeed or 400 if failed with error message explaining reason.
DELETE /projects/:id/repository/branches/:branch
```
-Parameters:
+| Attribute | Type | Required | Description |
+| --------- | ---- | -------- | ----------- |
+| `id` | integer | yes | The ID of a project |
+| `branch` | string | yes | The name of the branch |
-- `id` (required) - The ID of a project
-- `branch` (required) - The name of the branch
+It returns `200` if it succeeds, `404` if the branch to be deleted does not exist
+or `400` for other reasons. In case of an error, an explaining message is provided.
-It return 200 if succeed, 404 if the branch to be deleted does not exist
-or 400 for other reasons. In case of an error, an explaining message is provided.
+```bash
+curl -X DELETE -H "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v3/projects/5/repository/branches/newbranch"
+```
-Success response:
+Example response:
```json
{
- "branch_name": "my-removed-branch"
+ "branch_name": "newbranch"
}
```
diff --git a/doc/api/builds.md b/doc/api/builds.md
index ecb50754c8..d3ce72e59f 100644
--- a/doc/api/builds.md
+++ b/doc/api/builds.md
@@ -8,20 +8,16 @@ Get a list of builds in a project.
GET /projects/:id/builds
```
-### Parameters
-
-| Attribute | Type | required | Description |
+| Attribute | Type | Required | Description |
|-----------|---------|----------|---------------------|
-| id | integer | yes | The ID of a project |
-| scope | string|array of strings | no | The scope of builds to show, one or array of: `pending`, `running`, `failed`, `success`, `canceled`; showing all builds if none provided |
-
-### Example of request
+| `id` | integer | yes | The ID of a project |
+| `scope` | string **or** array of strings | no | The scope of builds to show, one or array of: `pending`, `running`, `failed`, `success`, `canceled`; showing all builds if none provided |
```
-curl -H "PRIVATE_TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v3/projects/1/builds"
+curl -H "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v3/projects/1/builds"
```
-### Example of response
+Example of response
```json
[
@@ -38,6 +34,10 @@ curl -H "PRIVATE_TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v3
"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",
@@ -76,6 +76,7 @@ curl -H "PRIVATE_TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v3
"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",
@@ -112,21 +113,17 @@ Get a list of builds for specific commit in a project.
GET /projects/:id/repository/commits/:sha/builds
```
-### Parameters
-
-| Attribute | Type | required | Description |
+| Attribute | Type | Required | Description |
|-----------|---------|----------|---------------------|
-| id | integer | yes | The ID of a project |
-| sha | string | yes | The SHA id of a commit |
-| scope | string|array of strings | no | The scope of builds to show, one or array of: `pending`, `running`, `failed`, `success`, `canceled`; showing all builds if none provided |
-
-### Example of request
+| `id` | integer | yes | The ID of a project |
+| `sha` | string | yes | The SHA id of a commit |
+| `scope` | string **or** array of strings | no | The scope of builds to show, one or array of: `pending`, `running`, `failed`, `success`, `canceled`; showing all builds if none provided |
```
-curl -H "PRIVATE_TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v3/projects/1/repository/commits/0ff3ae198f8601a285adcf5c0fff204ee6fba5fd/builds"
+curl -H "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v3/projects/1/repository/commits/0ff3ae198f8601a285adcf5c0fff204ee6fba5fd/builds"
```
-### Example of response
+Example of response
```json
[
@@ -143,6 +140,7 @@ curl -H "PRIVATE_TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v3
"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",
@@ -167,6 +165,7 @@ curl -H "PRIVATE_TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v3
"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",
@@ -203,20 +202,16 @@ Get a single build of a project
GET /projects/:id/builds/:build_id
```
-### 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
+| 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"
+curl -H "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v3/projects/1/builds/8"
```
-### Example of response
+Example of response
```json
{
@@ -232,6 +227,7 @@ curl -H "PRIVATE_TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v3
"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",
@@ -259,6 +255,34 @@ curl -H "PRIVATE_TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v3
}
```
+## 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
@@ -267,20 +291,16 @@ Cancel a single build of a project
POST /projects/:id/builds/:build_id/cancel
```
-### 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
+| Attribute | Type | Required | Description |
+|------------|---------|----------|---------------------|
+| `id` | integer | yes | The ID of a project |
+| `build_id` | integer | yes | The ID of a build |
```
-curl -X POST -H "PRIVATE_TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v3/projects/1/builds/1/cancel"
+curl -X POST -H "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v3/projects/1/builds/1/cancel"
```
-### Example of response
+Example of response
```json
{
@@ -296,6 +316,7 @@ curl -X POST -H "PRIVATE_TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.co
"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",
@@ -317,20 +338,16 @@ Retry a single build of a project
POST /projects/:id/builds/:build_id/retry
```
-### 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
+| Attribute | Type | Required | Description |
+|------------|---------|----------|---------------------|
+| `id` | integer | yes | The ID of a project |
+| `build_id` | integer | yes | The ID of a build |
```
-curl -X POST -H "PRIVATE_TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v3/projects/1/builds/1/retry"
+curl -X POST -H "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v3/projects/1/builds/1/retry"
```
-### Example of response
+Example of response
```json
{
@@ -346,6 +363,7 @@ curl -X POST -H "PRIVATE_TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.co
"coverage": null,
"created_at": "2016-01-11T10:13:33.506Z",
"download_url": null,
+ "artifacts_file": null,
"finished_at": null,
"id": 69,
"name": "rubocop",
@@ -358,3 +376,53 @@ curl -X POST -H "PRIVATE_TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.co
"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/commits.md b/doc/api/commits.md
index 93d62b751e..e4d436b8e5 100644
--- a/doc/api/commits.md
+++ b/doc/api/commits.md
@@ -8,10 +8,16 @@ Get a list of repository commits in a project.
GET /projects/:id/repository/commits
```
-Parameters:
+| Attribute | Type | Required | Description |
+| --------- | ---- | -------- | ----------- |
+| `id` | integer | yes | The ID of a project |
+| `ref_name` | string | no | The name of a repository branch or tag or if not given the default branch |
-- `id` (required) - The ID of a project
-- `ref_name` (optional) - The name of a repository branch or tag or if not given the default branch
+```bash
+curl -H "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v3/projects/5/repository/commits"
+```
+
+Example response:
```json
[
@@ -48,8 +54,16 @@ GET /projects/:id/repository/commits/:sha
Parameters:
-- `id` (required) - The ID of a project
-- `sha` (required) - The commit hash or name of a repository branch or tag
+| Attribute | Type | Required | Description |
+| --------- | ---- | -------- | ----------- |
+| `id` | integer | yes | The ID of a project |
+| `sha` | string | yes | The commit hash or name of a repository branch or tag |
+
+```bash
+curl -H "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v3/projects/5/repository/commits/master
+```
+
+Example response:
```json
{
@@ -79,8 +93,16 @@ GET /projects/:id/repository/commits/:sha/diff
Parameters:
-- `id` (required) - The ID of a project
-- `sha` (required) - The name of a repository branch or tag or if not given the default branch
+| Attribute | Type | Required | Description |
+| --------- | ---- | -------- | ----------- |
+| `id` | integer | yes | The ID of a project |
+| `sha` | string | yes | The commit hash or name of a repository branch or tag |
+
+```bash
+curl -H "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v3/projects/5/repository/commits/master/diff"
+```
+
+Example response:
```json
[
@@ -107,8 +129,16 @@ GET /projects/:id/repository/commits/:sha/comments
Parameters:
-- `id` (required) - The ID of a project
-- `sha` (required) - The name of a repository branch or tag or if not given the default branch
+| Attribute | Type | Required | Description |
+| --------- | ---- | -------- | ----------- |
+| `id` | integer | yes | The ID of a project |
+| `sha` | string | yes | The commit hash or name of a repository branch or tag |
+
+```bash
+curl -H "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v3/projects/5/repository/commits/master/comments"
+```
+
+Example response:
```json
[
@@ -128,39 +158,65 @@ Parameters:
## Post comment to commit
-Adds a comment to a commit. Optionally you can post comments on a specific line of a commit. Therefor both `path`, `line_new` and `line_old` are required.
+Adds a comment to a commit.
+
+In order to post a comment in a particular line of a particular file, you must
+specify the full commit SHA, the `path`, the `line` and `line_type` should be
+`new`.
+
+The comment will be added at the end of the last commit if at least one of the
+cases below is valid:
+
+- the `sha` is instead a branch or a tag and the `line` or `path` are invalid
+- the `line` number is invalid (does not exist)
+- the `path` is invalid (does not exist)
+
+In any of the above cases, the response of `line`, `line_type` and `path` is
+set to `null`.
```
POST /projects/:id/repository/commits/:sha/comments
```
-Parameters:
+| Attribute | Type | Required | Description |
+| --------- | ---- | -------- | ----------- |
+| `id` | integer | yes | The ID of a project |
+| `sha` | string | yes | The commit SHA or name of a repository branch or tag |
+| `note` | string | yes | The text of the comment |
+| `path` | string | no | The file path relative to the repository |
+| `line` | integer | no | The line number where the comment should be placed |
+| `line_type` | string | no | The line type. Takes `new` or `old` as arguments |
-- `id` (required) - The ID of a project
-- `sha` (required) - The name of a repository branch or tag or if not given the default branch
-- `note` (required) - Text of comment
-- `path` (optional) - The file path
-- `line` (optional) - The line number
-- `line_type` (optional) - The line type (new or old)
+```bash
+curl -X POST -H "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" -F "note=Nice picture man\!" -F "path=dudeism.md" -F "line=11" -F "line_type=new" https://gitlab.example.com/api/v3/projects/17/repository/commits/18f3e63d05582537db6d183d9d557be09e1f90c8/comments
+```
+
+Example response:
```json
{
- "author": {
- "id": 1,
- "username": "admin",
- "email": "admin@local.host",
- "name": "Administrator",
- "blocked": false,
- "created_at": "2012-04-29T08:46:00Z"
- },
- "note": "text1",
- "path": "example.rb",
- "line": 5,
- "line_type": "new"
+ "author" : {
+ "web_url" : "https://gitlab.example.com/u/thedude",
+ "avatar_url" : "https://gitlab.example.com/uploads/user/avatar/28/The-Big-Lebowski-400-400.png",
+ "username" : "thedude",
+ "state" : "active",
+ "name" : "Jeff Lebowski",
+ "id" : 28
+ },
+ "created_at" : "2016-01-19T09:44:55.600Z",
+ "line_type" : "new",
+ "path" : "dudeism.md",
+ "line" : 11,
+ "note" : "Nice picture man!"
}
```
-## Get the status of a commit
+## Commit status
+
+Since GitLab 8.1, this is the new commit status API. The documentation in
+[ci/api/commits](../ci/api/commits.md) is deprecated.
+
+### Get the status of a commit
Get the statuses of a commit in a project.
@@ -168,75 +224,116 @@ Get the statuses of a commit in a project.
GET /projects/:id/repository/commits/:sha/statuses
```
-Parameters:
+| Attribute | Type | Required | Description |
+| --------- | ---- | -------- | ----------- |
+| `id` | integer | yes | The ID of a project
+| `sha` | string | yes | The commit SHA
+| `ref_name`| string | no | The name of a repository branch or tag or, if not given, the default branch
+| `stage` | string | no | Filter by [build stage](../ci/yaml/README.md#stages), e.g., `test`
+| `name` | string | no | Filter by [job name](../ci/yaml/README.md#jobs), e.g., `bundler:audit`
+| `all` | boolean | no | Return all statuses, not only the latest ones
-- `id` (required) - The ID of a project
-- `sha` (required) - The commit SHA
-- `ref` (optional) - Filter by ref name, it can be branch or tag
-- `stage` (optional) - Filter by stage
-- `name` (optional) - Filer by status name, eg. jenkins
-- `all` (optional) - The flag to return all statuses, not only latest ones
+```bash
+curl -H "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v3/projects/17/repository/commits/18f3e63d05582537db6d183d9d557be09e1f90c8/statuses
+```
+
+Example response:
```json
[
- {
- "id": 13,
- "sha": "b0b3a907f41409829b307a28b82fdbd552ee5a27",
- "ref": "test",
- "status": "success",
- "name": "ci/jenkins",
- "target_url": "http://jenkins/project/url",
- "description": "Jenkins success",
- "created_at": "2015-10-12T09:47:16.250Z",
- "started_at": "2015-10-12T09:47:16.250Z",
- "finished_at": "2015-10-12T09:47:16.262Z",
- "author": {
- "id": 1,
- "username": "admin",
- "email": "admin@local.host",
- "name": "Administrator",
- "blocked": false,
- "created_at": "2012-04-29T08:46:00Z"
- }
- }
+ ...
+
+ {
+ "status" : "pending",
+ "created_at" : "2016-01-19T08:40:25.934Z",
+ "started_at" : null,
+ "name" : "bundler:audit",
+ "allow_failure" : true,
+ "author" : {
+ "username" : "thedude",
+ "state" : "active",
+ "web_url" : "https://gitlab.example.com/u/thedude",
+ "avatar_url" : "https://gitlab.example.com/uploads/user/avatar/28/The-Big-Lebowski-400-400.png",
+ "id" : 28,
+ "name" : "Jeff Lebowski"
+ },
+ "description" : null,
+ "sha" : "18f3e63d05582537db6d183d9d557be09e1f90c8",
+ "target_url" : "https://gitlab.example.com/thedude/gitlab-ce/builds/91",
+ "finished_at" : null,
+ "id" : 91,
+ "ref" : "master"
+ },
+ {
+ "started_at" : null,
+ "name" : "flay",
+ "allow_failure" : false,
+ "status" : "pending",
+ "created_at" : "2016-01-19T08:40:25.832Z",
+ "target_url" : "https://gitlab.example.com/thedude/gitlab-ce/builds/90",
+ "id" : 90,
+ "finished_at" : null,
+ "ref" : "master",
+ "sha" : "18f3e63d05582537db6d183d9d557be09e1f90c8",
+ "author" : {
+ "id" : 28,
+ "name" : "Jeff Lebowski",
+ "username" : "thedude",
+ "web_url" : "https://gitlab.example.com/u/thedude",
+ "state" : "active",
+ "avatar_url" : "https://gitlab.example.com/uploads/user/avatar/28/The-Big-Lebowski-400-400.png"
+ },
+ "description" : null
+ },
+
+ ...
]
```
-## Post the status to commit
+### Post the build status to a commit
-Adds or updates a status of a commit.
+Adds or updates a build status of a commit.
```
POST /projects/:id/statuses/:sha
```
-- `id` (required) - The ID of a project
-- `sha` (required) - The commit SHA
-- `state` (required) - The state of the status. Can be: pending, running, success, failed, canceled
-- `ref` (optional) - The ref (branch or tag) to which the status refers
-- `name` or `context` (optional) - The label to differentiate this status from the status of other systems. Default: "default"
-- `target_url` (optional) - The target URL to associate with this status
-- `description` (optional) - The short description of the status
+| Attribute | Type | Required | Description |
+| --------- | ---- | -------- | ----------- |
+| `id` | integer | yes | The ID of a project
+| `sha` | string | yes | The commit SHA
+| `state` | string | yes | The state of the status. Can be one of the following: `pending`, `running`, `success`, `failed`, `canceled`
+| `ref` | string | no | The `ref` (branch or tag) to which the status refers
+| `name` or `context` | string | no | The label to differentiate this status from the status of other systems. Default value is `default`
+| `target_url` | string | no | The target URL to associate with this status
+| `description` | string | no | The short description of the status
+
+```bash
+curl -X POST -H "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v3/projects/17/statuses/18f3e63d05582537db6d183d9d557be09e1f90c8?state=success"
+```
+
+Example response:
```json
{
- "id": 13,
- "sha": "b0b3a907f41409829b307a28b82fdbd552ee5a27",
- "ref": "test",
- "status": "success",
- "name": "ci/jenkins",
- "target_url": "http://jenkins/project/url",
- "description": "Jenkins success",
- "created_at": "2015-10-12T09:47:16.250Z",
- "started_at": "2015-10-12T09:47:16.250Z",
- "finished_at": "2015-10-12T09:47:16.262Z",
- "author": {
- "id": 1,
- "username": "admin",
- "email": "admin@local.host",
- "name": "Administrator",
- "blocked": false,
- "created_at": "2012-04-29T08:46:00Z"
- }
+ "author" : {
+ "web_url" : "https://gitlab.example.com/u/thedude",
+ "name" : "Jeff Lebowski",
+ "avatar_url" : "https://gitlab.example.com/uploads/user/avatar/28/The-Big-Lebowski-400-400.png",
+ "username" : "thedude",
+ "state" : "active",
+ "id" : 28
+ },
+ "name" : "default",
+ "sha" : "18f3e63d05582537db6d183d9d557be09e1f90c8",
+ "status" : "success",
+ "description" : null,
+ "id" : 93,
+ "target_url" : null,
+ "ref" : null,
+ "started_at" : null,
+ "created_at" : "2016-01-19T09:05:50.355Z",
+ "allow_failure" : false,
+ "finished_at" : "2016-01-19T09:05:50.365Z"
}
```
diff --git a/doc/api/deploy_key_multiple_projects.md b/doc/api/deploy_key_multiple_projects.md
index 1a5a458905..3ad836f51b 100644
--- a/doc/api/deploy_key_multiple_projects.md
+++ b/doc/api/deploy_key_multiple_projects.md
@@ -1,25 +1,29 @@
# Adding deploy keys to multiple projects
-If you want to easily add the same deploy key to multiple projects in the same group, this can be achieved quite easily with the API.
+If you want to easily add the same deploy key to multiple projects in the same
+group, this can be achieved quite easily with the API.
-First, find the ID of the projects you're interested in, by either listing all projects:
+First, find the ID of the projects you're interested in, by either listing all
+projects:
```
-curl --header 'PRIVATE-TOKEN: abcdef' https://gitlab.com/api/v3/projects
+curl -H 'PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK' https://gitlab.example.com/api/v3/projects
```
-Or finding the id of a group and then listing all projects in that group:
+Or finding the ID of a group and then listing all projects in that group:
```
-curl --header 'PRIVATE-TOKEN: abcdef' https://gitlab.com/api/v3/groups
+curl -H 'PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK' https://gitlab.example.com/api/v3/groups
# For group 1234:
-curl --header 'PRIVATE-TOKEN: abcdef' https://gitlab.com/api/v3/groups/1234
+curl -H 'PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK' https://gitlab.example.com/api/v3/groups/1234
```
With those IDs, add the same deploy key to all:
+
```
for project_id in 321 456 987; do
- curl -X POST --data '{"title": "my key", "key": "ssh-rsa AAAA..."}' --header 'PRIVATE-TOKEN: abcdef' https://gitlab.com/api/v3/projects/${project_id}/keys
+ curl -X POST -H "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" -H "Content-Type: application/json" \
+ --data '{"title": "my key", "key": "ssh-rsa AAAA..."}' https://gitlab.example.com/api/v3/projects/${project_id}/keys
done
```
diff --git a/doc/api/deploy_keys.md b/doc/api/deploy_keys.md
index e4492fc609..9da1fe22e6 100644
--- a/doc/api/deploy_keys.md
+++ b/doc/api/deploy_keys.md
@@ -8,9 +8,15 @@ Get a list of a project's deploy keys.
GET /projects/:id/keys
```
-Parameters:
+| Attribute | Type | Required | Description |
+| --------- | ---- | -------- | ----------- |
+| `id` | integer | yes | The ID of the project |
-- `id` (required) - The ID of the project
+```bash
+curl -H "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v3/projects/5/keys"
+```
+
+Example response:
```json
[
@@ -39,8 +45,16 @@ GET /projects/:id/keys/:key_id
Parameters:
-- `id` (required) - The ID of the project
-- `key_id` (required) - The ID of the deploy key
+| Attribute | Type | Required | Description |
+| --------- | ---- | -------- | ----------- |
+| `id` | integer | yes | The ID of the project |
+| `key_id` | integer | yes | The ID of the deploy key |
+
+```bash
+curl -H "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v3/projects/5/keys/11"
+```
+
+Example response:
```json
{
@@ -54,17 +68,34 @@ Parameters:
## Add deploy key
Creates a new deploy key for a project.
-If deploy key already exists in another project - it will be joined to project but only if original one was is accessible by same user
+
+If the deploy key already exists in another project, it will be joined to current
+project only if original one was is accessible by the same user.
```
POST /projects/:id/keys
```
-Parameters:
+| Attribute | Type | Required | Description |
+| --------- | ---- | -------- | ----------- |
+| `id` | integer | yes | The ID of the project |
+| `title` | string | yes | New deploy key's title |
+| `key` | string | yes | New deploy key |
-- `id` (required) - The ID of the project
-- `title` (required) - New deploy key's title
-- `key` (required) - New deploy key
+```bash
+curl -X POST -H "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" -H "Content-Type: application/json" --data '{"title": "My deploy key", "key": "ssh-rsa AAAA..."}' "https://gitlab.example.com/api/v3/projects/5/keys/"
+```
+
+Example response:
+
+```json
+{
+ "key" : "ssh-rsa AAAA...",
+ "id" : 12,
+ "title" : "My deploy key",
+ "created_at" : "2015-08-29T12:44:31.550Z"
+}
+```
## Delete deploy key
@@ -74,7 +105,26 @@ Delete a deploy key from a project
DELETE /projects/:id/keys/:key_id
```
-Parameters:
+| Attribute | Type | Required | Description |
+| --------- | ---- | -------- | ----------- |
+| `id` | integer | yes | The ID of the project |
+| `key_id` | integer | yes | The ID of the deploy key |
-- `id` (required) - The ID of the project
-- `key_id` (required) - The ID of the deploy key
+```bash
+curl -X DELETE -H "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v3/projects/5/keys/13"
+```
+
+Example response:
+
+```json
+{
+ "updated_at" : "2015-08-29T12:50:57.259Z",
+ "key" : "ssh-rsa AAAA...",
+ "public" : false,
+ "title" : "My deploy key",
+ "user_id" : null,
+ "created_at" : "2015-08-29T12:50:57.259Z",
+ "fingerprint" : "6a:33:1f:74:51:c0:39:81:79:ec:7a:31:f8:40:20:43",
+ "id" : 13
+}
+```
diff --git a/doc/api/groups.md b/doc/api/groups.md
index 808675d860..d47e79ba47 100644
--- a/doc/api/groups.md
+++ b/doc/api/groups.md
@@ -33,6 +33,7 @@ GET /groups/:id/projects
Parameters:
- `archived` (optional) - if passed, limit by archived status
+- `visibility` (optional) - if passed, limit by visibility `public`, `internal`, `private`
- `order_by` (optional) - Return requests ordered by `id`, `name`, `path`, `created_at`, `updated_at` or `last_activity_at` fields. Default is `created_at`
- `sort` (optional) - Return requests sorted in `asc` or `desc` order. Default is `desc`
- `search` (optional) - Return list of authorized projects according to a search criteria
diff --git a/doc/api/issues.md b/doc/api/issues.md
index d407bc35d7..9e704648b2 100644
--- a/doc/api/issues.md
+++ b/doc/api/issues.md
@@ -1,9 +1,20 @@
# Issues
+Every API call to issues must be authenticated.
+
+If a user is not a member of a project and the project is private, a `GET`
+request on that project will result to a `404` status code.
+
+## Issues pagination
+
+By default, `GET` requests return 20 results at a time because the API results
+are paginated.
+
+Read more on [pagination](README.md#pagination).
+
## List issues
-Get all issues created by authenticated user. This function takes pagination parameters
-`page` and `per_page` to restrict the list of issues.
+Get all issues created by the authenticated user.
```
GET /issues
@@ -14,81 +25,65 @@ GET /issues?labels=foo,bar
GET /issues?labels=foo,bar&state=opened
```
-Parameters:
+| Attribute | Type | Required | Description |
+| --------- | ---- | -------- | ----------- |
+| `state` | string | no | Return all issues or just those that are `opened` or `closed`|
+| `labels` | string | no | Comma-separated list of label names |
+| `order_by`| string | no | Return requests ordered by `created_at` or `updated_at` fields. Default is `created_at` |
+| `sort` | string | no | Return requests sorted in `asc` or `desc` order. Default is `desc` |
-- `state` (optional) - Return `all` issues or just those that are `opened` or `closed`
-- `labels` (optional) - Comma-separated list of label names
-- `order_by` (optional) - Return requests ordered by `created_at` or `updated_at` fields. Default is `created_at`
-- `sort` (optional) - Return requests sorted in `asc` or `desc` order. Default is `desc`
+```bash
+curl -H "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v3/issues
+```
+
+Example response:
```json
[
- {
- "id": 43,
- "iid": 3,
- "project_id": 8,
- "title": "4xx/5xx pages",
- "description": "",
- "labels": [],
- "milestone": null,
- "assignee": null,
- "author": {
- "id": 1,
- "username": "john_smith",
- "email": "john@example.com",
- "name": "John Smith",
- "state": "active",
- "created_at": "2012-05-23T08:00:58Z"
- },
- "state": "closed",
- "updated_at": "2012-07-02T17:53:12Z",
- "created_at": "2012-07-02T17:53:12Z"
- },
- {
- "id": 42,
- "iid": 4,
- "project_id": 8,
- "title": "Add user settings",
- "description": "",
- "labels": [
- "feature"
- ],
- "milestone": {
- "id": 1,
- "title": "v1.0",
- "description": "",
- "due_date": "2012-07-20",
- "state": "reopened",
- "updated_at": "2012-07-04T13:42:48Z",
- "created_at": "2012-07-04T13:42:48Z"
- },
- "assignee": {
- "id": 2,
- "username": "jack_smith",
- "email": "jack@example.com",
- "name": "Jack Smith",
- "state": "active",
- "created_at": "2012-05-23T08:01:01Z"
- },
- "author": {
- "id": 1,
- "username": "john_smith",
- "email": "john@example.com",
- "name": "John Smith",
- "state": "active",
- "created_at": "2012-05-23T08:00:58Z"
- },
- "state": "opened",
- "updated_at": "2012-07-12T13:43:19Z",
- "created_at": "2012-06-28T12:58:06Z"
- }
+ {
+ "state" : "opened",
+ "description" : "Ratione dolores corrupti mollitia soluta quia.",
+ "author" : {
+ "state" : "active",
+ "id" : 18,
+ "web_url" : "https://gitlab.example.com/u/eileen.lowe",
+ "name" : "Alexandra Bashirian",
+ "avatar_url" : null,
+ "username" : "eileen.lowe"
+ },
+ "milestone" : {
+ "project_id" : 1,
+ "description" : "Ducimus nam enim ex consequatur cumque ratione.",
+ "state" : "closed",
+ "due_date" : null,
+ "iid" : 2,
+ "created_at" : "2016-01-04T15:31:39.996Z",
+ "title" : "v4.0",
+ "id" : 17,
+ "updated_at" : "2016-01-04T15:31:39.996Z"
+ },
+ "project_id" : 1,
+ "assignee" : {
+ "state" : "active",
+ "id" : 1,
+ "name" : "Administrator",
+ "web_url" : "https://gitlab.example.com/u/root",
+ "avatar_url" : null,
+ "username" : "root"
+ },
+ "updated_at" : "2016-01-04T15:31:51.081Z",
+ "id" : 76,
+ "title" : "Consequatur vero maxime deserunt laboriosam est voluptas dolorem.",
+ "created_at" : "2016-01-04T15:31:51.081Z",
+ "iid" : 6,
+ "labels" : []
+ },
]
```
## List project issues
-Get a list of project issues. This function accepts pagination parameters `page` and `per_page`
-to return the list of project issues.
+Get a list of a project's issues.
```
GET /projects/:id/issues
@@ -102,67 +97,123 @@ GET /projects/:id/issues?milestone=1.0.0&state=opened
GET /projects/:id/issues?iid=42
```
-Parameters:
+| Attribute | Type | Required | Description |
+| --------- | ---- | -------- | ----------- |
+| `id` | integer | yes | The ID of a project |
+| `iid` | integer | no | Return the issue having the given `iid` |
+| `state` | string | no | Return all issues or just those that are `opened` or `closed`|
+| `labels` | string | no | Comma-separated list of label names |
+| `milestone` | string| no | The milestone title |
+| `order_by`| string | no | Return requests ordered by `created_at` or `updated_at` fields. Default is `created_at` |
+| `sort` | string | no | Return requests sorted in `asc` or `desc` order. Default is `desc` |
-- `id` (required) - The ID of a project
-- `iid` (optional) - Return the issue having the given `iid`
-- `state` (optional) - Return `all` issues or just those that are `opened` or `closed`
-- `labels` (optional) - Comma-separated list of label names
-- `milestone` (optional) - Milestone title
-- `order_by` (optional) - Return requests ordered by `created_at` or `updated_at` fields. Default is `created_at`
-- `sort` (optional) - Return requests sorted in `asc` or `desc` order. Default is `desc`
+
+```bash
+curl -H "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v3/projects/4/issues
+```
+
+Example response:
+
+```json
+[
+ {
+ "project_id" : 4,
+ "milestone" : {
+ "due_date" : null,
+ "project_id" : 4,
+ "state" : "closed",
+ "description" : "Rerum est voluptatem provident consequuntur molestias similique ipsum dolor.",
+ "iid" : 3,
+ "id" : 11,
+ "title" : "v3.0",
+ "created_at" : "2016-01-04T15:31:39.788Z",
+ "updated_at" : "2016-01-04T15:31:39.788Z"
+ },
+ "author" : {
+ "state" : "active",
+ "web_url" : "https://gitlab.example.com/u/root",
+ "avatar_url" : null,
+ "username" : "root",
+ "id" : 1,
+ "name" : "Administrator"
+ },
+ "description" : "Omnis vero earum sunt corporis dolor et placeat.",
+ "state" : "closed",
+ "iid" : 1,
+ "assignee" : {
+ "avatar_url" : null,
+ "web_url" : "https://gitlab.example.com/u/lennie",
+ "state" : "active",
+ "username" : "lennie",
+ "id" : 9,
+ "name" : "Dr. Luella Kovacek"
+ },
+ "labels" : [],
+ "id" : 41,
+ "title" : "Ut commodi ullam eos dolores perferendis nihil sunt.",
+ "updated_at" : "2016-01-04T15:31:46.176Z",
+ "created_at" : "2016-01-04T15:31:46.176Z"
+ }
+]
+```
## Single issue
-Gets a single project issue.
+Get a single project issue.
```
GET /projects/:id/issues/:issue_id
```
-Parameters:
+| Attribute | Type | Required | Description |
+| --------- | ---- | -------- | ----------- |
+| `id` | integer | yes | The ID of a project |
+| `issue_id`| integer | yes | The ID of a project's issue |
-- `id` (required) - The ID of a project
-- `issue_id` (required) - The ID of a project issue
+```bash
+curl -H "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v3/projects/4/issues/41
+```
+
+Example response:
```json
{
- "id": 42,
- "iid": 3,
- "project_id": 8,
- "title": "Add user settings",
- "description": "",
- "labels": [
- "feature"
- ],
- "milestone": {
- "id": 1,
- "title": "v1.0",
- "description": "",
- "due_date": "2012-07-20",
- "state": "closed",
- "updated_at": "2012-07-04T13:42:48Z",
- "created_at": "2012-07-04T13:42:48Z"
- },
- "assignee": {
- "id": 2,
- "username": "jack_smith",
- "email": "jack@example.com",
- "name": "Jack Smith",
- "state": "active",
- "created_at": "2012-05-23T08:01:01Z"
- },
- "author": {
- "id": 1,
- "username": "john_smith",
- "email": "john@example.com",
- "name": "John Smith",
- "state": "active",
- "created_at": "2012-05-23T08:00:58Z"
- },
- "state": "opened",
- "updated_at": "2012-07-12T13:43:19Z",
- "created_at": "2012-06-28T12:58:06Z"
+ "project_id" : 4,
+ "milestone" : {
+ "due_date" : null,
+ "project_id" : 4,
+ "state" : "closed",
+ "description" : "Rerum est voluptatem provident consequuntur molestias similique ipsum dolor.",
+ "iid" : 3,
+ "id" : 11,
+ "title" : "v3.0",
+ "created_at" : "2016-01-04T15:31:39.788Z",
+ "updated_at" : "2016-01-04T15:31:39.788Z"
+ },
+ "author" : {
+ "state" : "active",
+ "web_url" : "https://gitlab.example.com/u/root",
+ "avatar_url" : null,
+ "username" : "root",
+ "id" : 1,
+ "name" : "Administrator"
+ },
+ "description" : "Omnis vero earum sunt corporis dolor et placeat.",
+ "state" : "closed",
+ "iid" : 1,
+ "assignee" : {
+ "avatar_url" : null,
+ "web_url" : "https://gitlab.example.com/u/lennie",
+ "state" : "active",
+ "username" : "lennie",
+ "id" : 9,
+ "name" : "Dr. Luella Kovacek"
+ },
+ "labels" : [],
+ "id" : 41,
+ "title" : "Ut commodi ullam eos dolores perferendis nihil sunt.",
+ "updated_at" : "2016-01-04T15:31:46.176Z",
+ "created_at" : "2016-01-04T15:31:46.176Z"
}
```
@@ -170,57 +221,122 @@ Parameters:
Creates a new project issue.
+If the operation is successful, a status code of `200` and the newly-created
+issue is returned. If an error occurs, an error number and a message explaining
+the reason is returned.
+
```
POST /projects/:id/issues
```
-Parameters:
+| Attribute | Type | Required | Description |
+| --------- | ---- | -------- | ----------- |
+| `id` | integer | yes | The ID of a project |
+| `title` | string | yes | The title of an issue |
+| `description` | string | no | The description of an issue |
+| `assignee_id` | integer | no | The ID of a user to assign issue |
+| `milestone_id` | integer | no | The ID of a milestone to assign issue |
+| `labels` | string | no | Comma-separated label names for an issue |
-- `id` (required) - The ID of a project
-- `title` (required) - The title of an issue
-- `description` (optional) - The description of an issue
-- `assignee_id` (optional) - The ID of a user to assign issue
-- `milestone_id` (optional) - The ID of a milestone to assign issue
-- `labels` (optional) - Comma-separated label names for an issue
+```bash
+curl -X POST -H "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v3/projects/4/issues?title=Issues%20with%20auth&labels=bug
+```
-If the operation is successful, 200 and the newly created issue is returned.
-If an error occurs, an error number and a message explaining the reason is returned.
+Example response:
+
+```json
+{
+ "project_id" : 4,
+ "id" : 84,
+ "created_at" : "2016-01-07T12:44:33.959Z",
+ "iid" : 14,
+ "title" : "Issues with auth",
+ "state" : "opened",
+ "assignee" : null,
+ "labels" : [
+ "bug"
+ ],
+ "author" : {
+ "name" : "Alexandra Bashirian",
+ "avatar_url" : null,
+ "state" : "active",
+ "web_url" : "https://gitlab.example.com/u/eileen.lowe",
+ "id" : 18,
+ "username" : "eileen.lowe"
+ },
+ "description" : null,
+ "updated_at" : "2016-01-07T12:44:33.959Z",
+ "milestone" : null
+}
+```
## Edit issue
-Updates an existing project issue. This function is also used to mark an issue as closed.
+Updates an existing project issue. This call is also used to mark an issue as
+closed.
+
+If the operation is successful, a code of `200` and the updated issue is
+returned. If an error occurs, an error number and a message explaining the
+reason is returned.
```
PUT /projects/:id/issues/:issue_id
```
-Parameters:
+| Attribute | Type | Required | Description |
+| --------- | ---- | -------- | ----------- |
+| `id` | integer | yes | The ID of a project |
+| `issue_id` | integer | yes | The ID of a project's issue |
+| `title` | string | no | The title of an issue |
+| `description` | string | no | The description of an issue |
+| `assignee_id` | integer | no | The ID of a user to assign the issue to |
+| `milestone_id` | integer | no | The ID of a milestone to assign the issue to |
+| `labels` | string | no | Comma-separated label names for an issue |
+| `state_event` | string | no | The state event of an issue. Set `close` to close the issue and `reopen` to reopen it |
-- `id` (required) - The ID of a project
-- `issue_id` (required) - The ID of a project's issue
-- `title` (optional) - The title of an issue
-- `description` (optional) - The description of an issue
-- `assignee_id` (optional) - The ID of a user to assign issue
-- `milestone_id` (optional) - The ID of a milestone to assign issue
-- `labels` (optional) - Comma-separated label names for an issue
-- `state_event` (optional) - The state event of an issue ('close' to close issue and 'reopen' to reopen it)
+```bash
+curl -X PUT -H "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v3/projects/4/issues/85?state_event=close
+```
-If the operation is successful, 200 and the updated issue is returned.
-If an error occurs, an error number and a message explaining the reason is returned.
+Example response:
+
+```json
+{
+ "created_at" : "2016-01-07T12:46:01.410Z",
+ "author" : {
+ "name" : "Alexandra Bashirian",
+ "avatar_url" : null,
+ "username" : "eileen.lowe",
+ "id" : 18,
+ "state" : "active",
+ "web_url" : "https://gitlab.example.com/u/eileen.lowe"
+ },
+ "state" : "closed",
+ "title" : "Issues with auth",
+ "project_id" : 4,
+ "description" : null,
+ "updated_at" : "2016-01-07T12:55:16.213Z",
+ "iid" : 15,
+ "labels" : [
+ "bug"
+ ],
+ "id" : 85,
+ "assignee" : null,
+ "milestone" : null
+}
+```
## Delete existing issue (**Deprecated**)
-The function is deprecated and returns a `405 Method Not Allowed` error if called. An issue gets now closed and is done by calling `PUT /projects/:id/issues/:issue_id` with parameter `state_event` set to `close`.
+This call is deprecated and returns a `405 Method Not Allowed` error if called.
+An issue gets now closed and is done by calling
+`PUT /projects/:id/issues/:issue_id` with the parameter `state_event` set to
+`close`. See [edit issue](#edit-issue) for more details.
```
DELETE /projects/:id/issues/:issue_id
```
-Parameters:
-
-- `id` (required) - The project ID
-- `issue_id` (required) - The ID of the issue
-
## Comments on issues
-Comments are done via the notes resource.
+Comments are done via the [notes](notes.md) resource.
diff --git a/doc/api/labels.md b/doc/api/labels.md
index de41f35d28..6496ffe9fd 100644
--- a/doc/api/labels.md
+++ b/doc/api/labels.md
@@ -2,83 +2,153 @@
## List labels
-Get all labels for given project.
+Get all labels for a given project.
```
GET /projects/:id/labels
```
+| Attribute | Type | Required | Description |
+| --------- | ---- | -------- | ----------- |
+| `id` | integer | yes | The ID of the project |
+
+```bash
+curl -H "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v3/projects/1/labels
+```
+
+Example response:
+
```json
[
- {
- "name": "Awesome",
- "color": "#DD10AA"
- },
- {
- "name": "Documentation",
- "color": "#1E80DD"
- },
- {
- "name": "Feature",
- "color": "#11FF22"
- },
- {
- "name": "Bug",
- "color": "#EE1122"
- }
+ {
+ "name" : "bug",
+ "color" : "#d9534f"
+ },
+ {
+ "color" : "#d9534f",
+ "name" : "confirmed"
+ },
+ {
+ "name" : "critical",
+ "color" : "#d9534f"
+ },
+ {
+ "color" : "#428bca",
+ "name" : "discussion"
+ },
+ {
+ "name" : "documentation",
+ "color" : "#f0ad4e"
+ },
+ {
+ "color" : "#5cb85c",
+ "name" : "enhancement"
+ },
+ {
+ "color" : "#428bca",
+ "name" : "suggestion"
+ },
+ {
+ "color" : "#f0ad4e",
+ "name" : "support"
+ }
]
```
## Create a new label
-Creates a new label for given repository with given name and color.
+Creates a new label for the given repository with the given name and color.
+
+It returns 200 if the label was successfully created, 400 for wrong parameters
+and 409 if the label already exists.
```
POST /projects/:id/labels
```
-Parameters:
+| Attribute | Type | Required | Description |
+| --------- | ---- | -------- | ----------- |
+| `id` | integer | yes | The ID of the project |
+| `name` | string | yes | The name of the label |
+| `color` | string | yes | The color of the label in 6-digit hex notation with leading `#` sign |
-- `id` (required) - The ID of a project
-- `name` (required) - The name of the label
-- `color` (required) - Color of the label given in 6-digit hex notation with leading '#' sign (e.g. #FFAABB)
+```bash
+curl --data "name=feature&color=#5843AD" -H "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v3/projects/1/labels"
+```
-It returns 200 and the newly created label, if the operation succeeds.
-If the label already exists, 409 and an error message is returned.
-If label parameters are invalid, 400 and an explaining error message is returned.
+Example response:
+
+```json
+{
+ "name" : "feature",
+ "color" : "#5843AD"
+}
+```
## Delete a label
-Deletes a label given by its name.
+Deletes a label with a given name.
+
+It returns 200 if the label was successfully deleted, 400 for wrong parameters
+and 404 if the label does not exist.
+In case of an error, an additional error message is returned.
```
DELETE /projects/:id/labels
```
-- `id` (required) - The ID of a project
-- `name` (required) - The name of the label to be deleted
+| Attribute | Type | Required | Description |
+| --------- | ---- | -------- | ----------- |
+| `id` | integer | yes | The ID of the project |
+| `name` | string | yes | The name of the label |
-It returns 200 if the label successfully was deleted, 400 for wrong parameters
-and 404 if the label does not exist.
-In case of an error, additionally an error message is returned.
+```bash
+curl -X DELETE -H "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v3/projects/1/labels?name=bug"
+```
+
+Example response:
+
+```json
+{
+ "title" : "feature",
+ "color" : "#5843AD",
+ "updated_at" : "2015-11-03T21:22:30.737Z",
+ "template" : false,
+ "project_id" : 1,
+ "created_at" : "2015-11-03T21:22:30.737Z",
+ "id" : 9
+}
+```
## Edit an existing label
-Updates an existing label with new name or now color. At least one parameter
+Updates an existing label with new name or new color. At least one parameter
is required, to update the label.
+It returns 200 if the label was successfully deleted, 400 for wrong parameters
+and 404 if the label does not exist.
+In case of an error, an additional error message is returned.
+
```
PUT /projects/:id/labels
```
-Parameters:
+| Attribute | Type | Required | Description |
+| --------- | ---- | -------- | ----------- |
+| `id` | integer | yes | The ID of the project |
+| `name` | string | yes | The name of the existing label |
+| `new_name` | string | yes if `color` if not provided | The new name of the label |
+| `color` | string | yes if `new_name` is not provided | The new color of the label in 6-digit hex notation with leading `#` sign |
-- `id` (required) - The ID of a project
-- `name` (required) - The name of the existing label
-- `new_name` (optional) - The new name of the label
-- `color` (optional) - New color of the label given in 6-digit hex notation with leading '#' sign (e.g. #FFAABB)
+```bash
+curl -X PUT --data "name=documentation&new_name=docs&color=#8E44AD" -H "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v3/projects/1/labels"
+```
-On success, this method returns 200 with the updated label.
-If required parameters are missing or parameters are invalid, 400 is returned.
-If the label to be updated is missing, 404 is returned.
-In case of an error, additionally an error message is returned.
+Example response:
+
+```json
+{
+ "color" : "#8E44AD",
+ "name" : "docs"
+}
+```
diff --git a/doc/api/merge_requests.md b/doc/api/merge_requests.md
index 8bc0a67067..009532b50c 100644
--- a/doc/api/merge_requests.md
+++ b/doc/api/merge_requests.md
@@ -2,7 +2,7 @@
## List merge requests
-Get all merge requests for this project.
+Get all merge requests for this project.
The `state` parameter can be used to get only merge requests with a given state (`opened`, `closed`, or `merged`) or all of them (`all`).
The pagination parameters `page` and `per_page` can be used to restrict the list of merge requests.
@@ -49,8 +49,24 @@ Parameters:
"state": "active",
"created_at": "2012-04-29T08:46:00Z"
},
+ "source_project_id": "2",
+ "target_project_id": "3",
+ "labels": [ ],
"description":"fixed login page css paddings",
- "work_in_progress": false
+ "work_in_progress": false,
+ "milestone": {
+ "id": 5,
+ "iid": 1,
+ "project_id": 3,
+ "title": "v2.0",
+ "description": "Assumenda aut placeat expedita exercitationem labore sunt enim earum.",
+ "state": "closed",
+ "created_at": "2015-02-02T19:49:26.013Z",
+ "updated_at": "2015-02-02T19:49:26.013Z",
+ "due_date": null
+ },
+ "merge_when_build_succeeds": true,
+ "merge_status": "can_be_merged"
}
]
```
@@ -60,7 +76,7 @@ Parameters:
Shows information about a single merge request.
```
-GET /projects/:id/merge_request/:merge_request_id
+GET /projects/:id/merge_requests/:merge_request_id
```
Parameters:
@@ -95,8 +111,24 @@ Parameters:
"state": "active",
"created_at": "2012-04-29T08:46:00Z"
},
+ "source_project_id": "2",
+ "target_project_id": "3",
+ "labels": [ ],
"description":"fixed login page css paddings",
- "work_in_progress": false
+ "work_in_progress": false,
+ "milestone": {
+ "id": 5,
+ "iid": 1,
+ "project_id": 3,
+ "title": "v2.0",
+ "description": "Assumenda aut placeat expedita exercitationem labore sunt enim earum.",
+ "state": "closed",
+ "created_at": "2015-02-02T19:49:26.013Z",
+ "updated_at": "2015-02-02T19:49:26.013Z",
+ "due_date": null
+ },
+ "merge_when_build_succeeds": true,
+ "merge_status": "can_be_merged"
}
```
@@ -105,7 +137,7 @@ Parameters:
Get a list of merge request commits.
```
-GET /projects/:id/merge_request/:merge_request_id/commits
+GET /projects/:id/merge_requests/:merge_request_id/commits
```
Parameters:
@@ -142,7 +174,7 @@ Parameters:
Shows information about the merge request including its files and changes.
```
-GET /projects/:id/merge_request/:merge_request_id/changes
+GET /projects/:id/merge_requests/:merge_request_id/changes
```
Parameters:
@@ -156,8 +188,6 @@ Parameters:
"iid": 1,
"project_id": 4,
"title": "Blanditiis beatae suscipit hic assumenda et molestias nisi asperiores repellat et.",
- "description": "Qui voluptatibus placeat ipsa alias quasi. Deleniti rem ut sint. Optio velit qui distinctio.",
- "work_in_progress": false,
"state": "reopened",
"created_at": "2015-02-02T19:49:39.159Z",
"updated_at": "2015-02-02T20:08:49.959Z",
@@ -182,6 +212,8 @@ Parameters:
"source_project_id": 4,
"target_project_id": 4,
"labels": [ ],
+ "description": "Qui voluptatibus placeat ipsa alias quasi. Deleniti rem ut sint. Optio velit qui distinctio.",
+ "work_in_progress": false,
"milestone": {
"id": 5,
"iid": 1,
@@ -193,6 +225,8 @@ Parameters:
"updated_at": "2015-02-02T19:49:26.013Z",
"due_date": null
},
+ "merge_when_build_succeeds": true,
+ "merge_status": "can_be_merged",
"changes": [
{
"old_path": "VERSION",
@@ -225,6 +259,7 @@ Parameters:
- `description` (optional) - Description of MR
- `target_project_id` (optional) - The target project (numeric id)
- `labels` (optional) - Labels for MR as a comma-separated list
+- `milestone_id` (optional) - Milestone ID
```json
{
@@ -252,7 +287,24 @@ Parameters:
"state": "active",
"created_at": "2012-04-29T08:46:00Z"
},
- "description":"fixed login page css paddings"
+ "source_project_id": 4,
+ "target_project_id": 4,
+ "labels": [ ],
+ "description":"fixed login page css paddings",
+ "work_in_progress": false,
+ "milestone": {
+ "id": 5,
+ "iid": 1,
+ "project_id": 4,
+ "title": "v2.0",
+ "description": "Assumenda aut placeat expedita exercitationem labore sunt enim earum.",
+ "state": "closed",
+ "created_at": "2015-02-02T19:49:26.013Z",
+ "updated_at": "2015-02-02T19:49:26.013Z",
+ "due_date": null
+ },
+ "merge_when_build_succeeds": true,
+ "merge_status": "can_be_merged"
}
```
@@ -264,7 +316,7 @@ If an error occurs, an error number and a message explaining the reason is retur
Updates an existing merge request. You can change the target branch, title, or even close the MR.
```
-PUT /projects/:id/merge_request/:merge_request_id
+PUT /projects/:id/merge_requests/:merge_request_id
```
Parameters:
@@ -277,6 +329,7 @@ Parameters:
- `description` - Description of MR
- `state_event` - New state (close|reopen|merge)
- `labels` (optional) - Labels for MR as a comma-separated list
+- `milestone_id` (optional) - Milestone ID
```json
{
@@ -284,7 +337,6 @@ Parameters:
"target_branch": "master",
"project_id": 3,
"title": "test1",
- "description": "description1",
"state": "opened",
"upvotes": 0,
"downvotes": 0,
@@ -303,7 +355,25 @@ Parameters:
"name": "Administrator",
"state": "active",
"created_at": "2012-04-29T08:46:00Z"
- }
+ },
+ "source_project_id": 4,
+ "target_project_id": 4,
+ "labels": [ ],
+ "description": "description1",
+ "work_in_progress": false,
+ "milestone": {
+ "id": 5,
+ "iid": 1,
+ "project_id": 4,
+ "title": "v2.0",
+ "description": "Assumenda aut placeat expedita exercitationem labore sunt enim earum.",
+ "state": "closed",
+ "created_at": "2015-02-02T19:49:26.013Z",
+ "updated_at": "2015-02-02T19:49:26.013Z",
+ "due_date": null
+ },
+ "merge_when_build_succeeds": true,
+ "merge_status": "can_be_merged"
}
```
@@ -323,7 +393,7 @@ If merge request is already merged or closed - you get 405 and error message 'Me
If you don't have permissions to accept this merge request - you'll get a 401
```
-PUT /projects/:id/merge_request/:merge_request_id/merge
+PUT /projects/:id/merge_requests/:merge_request_id/merge
```
Parameters:
@@ -359,7 +429,25 @@ Parameters:
"name": "Administrator",
"state": "active",
"created_at": "2012-04-29T08:46:00Z"
- }
+ },
+ "source_project_id": 4,
+ "target_project_id": 4,
+ "labels": [ ],
+ "description":"fixed login page css paddings",
+ "work_in_progress": false,
+ "milestone": {
+ "id": 5,
+ "iid": 1,
+ "project_id": 4,
+ "title": "v2.0",
+ "description": "Assumenda aut placeat expedita exercitationem labore sunt enim earum.",
+ "state": "closed",
+ "created_at": "2015-02-02T19:49:26.013Z",
+ "updated_at": "2015-02-02T19:49:26.013Z",
+ "due_date": null
+ },
+ "merge_when_build_succeeds": true,
+ "merge_status": "can_be_merged"
}
```
@@ -373,7 +461,7 @@ If the merge request is already merged or closed - you get 405 and error message
In case the merge request is not set to be merged when the build succeeds, you'll also get a 406 error.
```
-PUT /projects/:id/merge_request/:merge_request_id/cancel_merge_when_build_succeeds
+PUT /projects/:id/merge_requests/:merge_request_id/cancel_merge_when_build_succeeds
```
Parameters:
@@ -387,7 +475,7 @@ Parameters:
"source_branch": "test1",
"project_id": 3,
"title": "test1",
- "state": "merged",
+ "state": "opened",
"upvotes": 0,
"downvotes": 0,
"author": {
@@ -405,70 +493,90 @@ Parameters:
"name": "Administrator",
"state": "active",
"created_at": "2012-04-29T08:46:00Z"
- }
-}
-```
-
-## Post comment to MR
-
-Adds a comment to a merge request.
-
-```
-POST /projects/:id/merge_request/:merge_request_id/comments
-```
-
-Parameters:
-
-- `id` (required) - The ID of a project
-- `merge_request_id` (required) - ID of merge request
-- `note` (required) - Text of comment
-
-```json
-{
- "note": "text1"
-}
-```
-
-## Get the comments on a MR
-
-Gets all the comments associated with a merge request.
-
-```
-GET /projects/:id/merge_request/:merge_request_id/comments
-```
-
-Parameters:
-
-- `id` (required) - The ID of a project
-- `merge_request_id` (required) - ID of merge request
-
-```json
-[
- {
- "note": "this is the 1st comment on the 2merge merge request",
- "author": {
- "id": 11,
- "username": "admin",
- "email": "admin@example.com",
- "name": "Administrator",
- "state": "active",
- "created_at": "2014-03-06T08:17:35.000Z"
- }
},
- {
- "note": "Status changed to closed",
- "author": {
- "id": 11,
- "username": "admin",
- "email": "admin@example.com",
- "name": "Administrator",
- "state": "active",
- "created_at": "2014-03-06T08:17:35.000Z"
- }
- }
-]
+ "source_project_id": 4,
+ "target_project_id": 4,
+ "labels": [ ],
+ "description":"fixed login page css paddings",
+ "work_in_progress": false,
+ "milestone": {
+ "id": 5,
+ "iid": 1,
+ "project_id": 4,
+ "title": "v2.0",
+ "description": "Assumenda aut placeat expedita exercitationem labore sunt enim earum.",
+ "state": "closed",
+ "created_at": "2015-02-02T19:49:26.013Z",
+ "updated_at": "2015-02-02T19:49:26.013Z",
+ "due_date": null
+ },
+ "merge_when_build_succeeds": true,
+ "merge_status": "can_be_merged"
+}
```
## Comments on merge requets
-Comments are done via the notes resource.
+Comments are done via the [notes](notes.md) resource.
+
+## List issues that will close on merge
+
+Get all the issues that would be closed by merging the provided merge request.
+
+```
+GET /projects/:id/merge_requests/:merge_request_id/closes_issues
+```
+
+| Attribute | Type | Required | Description |
+| --------- | ---- | -------- | ----------- |
+| `id` | integer | yes | The ID of a project |
+| `merge_request_id` | integer | yes | The ID of the merge request |
+
+```bash
+curl -H "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v3/projects/76/merge_requests/1/closes_issues
+```
+
+Example response:
+
+```json
+[
+ {
+ "state" : "opened",
+ "description" : "Ratione dolores corrupti mollitia soluta quia.",
+ "author" : {
+ "state" : "active",
+ "id" : 18,
+ "web_url" : "https://gitlab.example.com/u/eileen.lowe",
+ "name" : "Alexandra Bashirian",
+ "avatar_url" : null,
+ "username" : "eileen.lowe"
+ },
+ "milestone" : {
+ "project_id" : 1,
+ "description" : "Ducimus nam enim ex consequatur cumque ratione.",
+ "state" : "closed",
+ "due_date" : null,
+ "iid" : 2,
+ "created_at" : "2016-01-04T15:31:39.996Z",
+ "title" : "v4.0",
+ "id" : 17,
+ "updated_at" : "2016-01-04T15:31:39.996Z"
+ },
+ "project_id" : 1,
+ "assignee" : {
+ "state" : "active",
+ "id" : 1,
+ "name" : "Administrator",
+ "web_url" : "https://gitlab.example.com/u/root",
+ "avatar_url" : null,
+ "username" : "root"
+ },
+ "updated_at" : "2016-01-04T15:31:51.081Z",
+ "id" : 76,
+ "title" : "Consequatur vero maxime deserunt laboriosam est voluptas dolorem.",
+ "created_at" : "2016-01-04T15:31:51.081Z",
+ "iid" : 6,
+ "labels" : []
+ },
+]
+```
diff --git a/doc/api/namespaces.md b/doc/api/namespaces.md
index 7b3238441f..42d9ce3d39 100644
--- a/doc/api/namespaces.md
+++ b/doc/api/namespaces.md
@@ -1,13 +1,29 @@
# Namespaces
+Usernames and groupnames fall under a special category called namespaces.
+
+For users and groups supported API calls see the [users](users.md) and
+[groups](groups.md) documentation respectively.
+
+[Pagination](README.md#pagination) is used.
+
## List namespaces
-Get a list of namespaces. (As user: my namespaces, as admin: all namespaces)
+Get a list of the namespaces of the authenticated user. If the user is an
+administrator, a list of all namespaces in the GitLab instance is shown.
```
GET /namespaces
```
+Example request:
+
+```bash
+curl -H "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v3/namespaces
+```
+
+Example response:
+
```json
[
{
@@ -23,22 +39,32 @@ GET /namespaces
]
```
-You can search for namespaces by name or path, see below.
-
## Search for namespace
-Get all namespaces that match your string in their name or path.
+Get all namespaces that match a string in their name or path.
```
GET /namespaces?search=foobar
```
+| Attribute | Type | Required | Description |
+| --------- | ---- | -------- | ----------- |
+| `search` | string | no | Returns a list of namespaces the user is authorized to see based on the search criteria |
+
+Example request:
+
+```bash
+curl -H "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v3/namespaces?search=twitter
+```
+
+Example response:
+
```json
[
{
- "id": 1,
- "path": "user1",
- "kind": "user"
+ "id": 4,
+ "path": "twitter",
+ "kind": "group"
}
]
```
diff --git a/doc/api/projects.md b/doc/api/projects.md
index 241229221d..9e9486cd87 100644
--- a/doc/api/projects.md
+++ b/doc/api/projects.md
@@ -29,6 +29,7 @@ GET /projects
Parameters:
- `archived` (optional) - if passed, limit by archived status
+- `visibility` (optional) - if passed, limit by visibility `public`, `internal`, `private`
- `order_by` (optional) - Return requests ordered by `id`, `name`, `path`, `created_at`, `updated_at` or `last_activity_at` fields. Default is `created_at`
- `sort` (optional) - Return requests sorted in `asc` or `desc` order. Default is `desc`
- `search` (optional) - Return list of authorized projects according to a search criteria
@@ -79,7 +80,9 @@ Parameters:
"avatar_url": "http://example.com/uploads/project/avatar/4/uploads/avatar.png",
"shared_runners_enabled": true,
"forks_count": 0,
- "star_count": 0
+ "star_count": 0,
+ "runners_token": "b8547b1dc37721d05889db52fa2f02",
+ "public_builds": true
},
{
"id": 6,
@@ -136,7 +139,8 @@ Parameters:
"shared_runners_enabled": true,
"forks_count": 0,
"star_count": 0,
- "runners_token": "b8547b1dc37721d05889db52fa2f02"
+ "runners_token": "b8547b1dc37721d05889db52fa2f02",
+ "public_builds": true
}
]
```
@@ -152,6 +156,7 @@ GET /projects/owned
Parameters:
- `archived` (optional) - if passed, limit by archived status
+- `visibility` (optional) - if passed, limit by visibility `public`, `internal`, `private`
- `order_by` (optional) - Return requests ordered by `id`, `name`, `path`, `created_at`, `updated_at` or `last_activity_at` fields. Default is `created_at`
- `sort` (optional) - Return requests sorted in `asc` or `desc` order. Default is `desc`
- `search` (optional) - Return list of authorized projects according to a search criteria
@@ -167,6 +172,7 @@ GET /projects/starred
Parameters:
- `archived` (optional) - if passed, limit by archived status
+- `visibility` (optional) - if passed, limit by visibility `public`, `internal`, `private`
- `order_by` (optional) - Return requests ordered by `id`, `name`, `path`, `created_at`, `updated_at` or `last_activity_at` fields. Default is `created_at`
- `sort` (optional) - Return requests sorted in `asc` or `desc` order. Default is `desc`
- `search` (optional) - Return list of authorized projects according to a search criteria
@@ -182,6 +188,7 @@ GET /projects/all
Parameters:
- `archived` (optional) - if passed, limit by archived status
+- `visibility` (optional) - if passed, limit by visibility `public`, `internal`, `private`
- `order_by` (optional) - Return requests ordered by `id`, `name`, `path`, `created_at`, `updated_at` or `last_activity_at` fields. Default is `created_at`
- `sort` (optional) - Return requests sorted in `asc` or `desc` order. Default is `desc`
- `search` (optional) - Return list of authorized projects according to a search criteria
@@ -420,6 +427,7 @@ Parameters:
- `public` (optional) - if `true` same as setting visibility_level = 20
- `visibility_level` (optional)
- `import_url` (optional)
+- `public_builds` (optional)
### Create project for user
@@ -442,6 +450,7 @@ Parameters:
- `public` (optional) - if `true` same as setting visibility_level = 20
- `visibility_level` (optional)
- `import_url` (optional)
+- `public_builds` (optional)
### Edit project
@@ -465,6 +474,7 @@ Parameters:
- `snippets_enabled` (optional)
- `public` (optional) - if `true` same as setting visibility_level = 20
- `visibility_level` (optional)
+- `public_builds` (optional)
On success, method returns 200 with the updated project. If parameters are
invalid, 400 is returned.
diff --git a/doc/api/runners.md b/doc/api/runners.md
new file mode 100644
index 0000000000..cc6c6b7cb2
--- /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/api/session.md b/doc/api/session.md
index 47c1c8a7a4..71e93d0bb0 100644
--- a/doc/api/session.md
+++ b/doc/api/session.md
@@ -1,39 +1,47 @@
# Session
-Login to get private token
+You can login with both GitLab and LDAP credentials in order to obtain the
+private token.
```
POST /session
```
-Parameters:
+| Attribute | Type | Required | Description |
+| ---------- | ------- | -------- | -------- |
+| `login` | string | yes | The username of the user|
+| `email` | string | yes if login is not provided | The email of the user |
+| `password` | string | yes | The password of the user |
-- `login` (required) - The login of user
-- `email` (required if login missing) - The email of user
-- `password` (required) - Valid password
-
-**You can login with both GitLab and LDAP credentials now**
+```bash
+curl -X POST "https://gitlab.example.com/api/v3/session?login=john_smith&password=strongpassw0rd"
+```
+Example response:
```json
{
- "id": 1,
- "username": "john_smith",
- "email": "john@example.com",
"name": "John Smith",
- "private_token": "dd34asd13as",
- "blocked": false,
- "created_at": "2012-05-23T08:00:58Z",
+ "username": "john_smith",
+ "id": 32,
+ "state": "active",
+ "avatar_url": null,
+ "created_at": "2015-01-29T21:07:19.440Z",
+ "is_admin": true,
"bio": null,
"skype": "",
"linkedin": "",
"twitter": "",
"website_url": "",
- "dark_scheme": false,
+ "email": "john@example.com",
"theme_id": 1,
- "is_admin": false,
+ "color_scheme_id": 1,
+ "projects_limit": 10,
+ "current_sign_in_at": "2015-07-07T07:10:58.392Z",
+ "identities": [],
"can_create_group": true,
- "can_create_team": true,
- "can_create_project": true
+ "can_create_project": true,
+ "two_factor_enabled": false,
+ "private_token": "9koXpg98eAheJpvBs5tK"
}
```
diff --git a/doc/api/settings.md b/doc/api/settings.md
index 96867c6791..001de76c7a 100644
--- a/doc/api/settings.md
+++ b/doc/api/settings.md
@@ -1,67 +1,77 @@
# Application settings
-This API allows you to read and modify GitLab instance application settings.
+These API calls allow you to read and modify GitLab instance application
+settings as appear in `/admin/application_settings`. You have to be an
+administrator in order to perform this action.
+## Get current application settings
-## Get current application settings:
+List the current application settings of the GitLab instance.
```
GET /application/settings
```
+```bash
+curl -H "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v3/application/settings
+```
+
+Example response:
+
```json
{
- "id": 1,
- "default_projects_limit": 10,
- "signup_enabled": true,
- "signin_enabled": true,
- "gravatar_enabled": true,
- "sign_in_text": "",
- "created_at": "2015-06-12T15:51:55.432Z",
- "updated_at": "2015-06-30T13:22:42.210Z",
- "home_page_url": "",
- "default_branch_protection": 2,
- "twitter_sharing_enabled": true,
- "restricted_visibility_levels": [],
- "max_attachment_size": 10,
- "session_expire_delay": 10080,
- "default_project_visibility": 0,
- "default_snippet_visibility": 0,
- "restricted_signup_domains": [],
- "user_oauth_applications": true,
- "after_sign_out_path": ""
+ "default_projects_limit" : 10,
+ "signup_enabled" : true,
+ "id" : 1,
+ "default_branch_protection" : 2,
+ "restricted_visibility_levels" : [],
+ "signin_enabled" : true,
+ "twitter_sharing_enabled" : true,
+ "after_sign_out_path" : null,
+ "max_attachment_size" : 10,
+ "user_oauth_applications" : true,
+ "updated_at" : "2016-01-04T15:44:55.176Z",
+ "session_expire_delay" : 10080,
+ "home_page_url" : null,
+ "default_snippet_visibility" : 0,
+ "restricted_signup_domains" : [],
+ "created_at" : "2016-01-04T15:44:55.176Z",
+ "default_project_visibility" : 0,
+ "gravatar_enabled" : true,
+ "sign_in_text" : null
}
```
-## Change application settings:
-
-
+## Change application settings
```
PUT /application/settings
```
-Parameters:
+| Attribute | Type | Required | Description |
+| --------- | ---- | :------: | ----------- |
+| `default_projects_limit` | integer | no | Project limit per user. Default is `10` |
+| `signup_enabled` | boolean | no | Enable registration. Default is `true`. |
+| `signin_enabled` | boolean | no | Enable login via a GitLab account. Default is `true`. |
+| `gravatar_enabled` | boolean | no | Enable Gravatar |
+| `sign_in_text` | string | no | Text on login page |
+| `home_page_url` | string | no | Redirect to this URL when not logged in |
+| `default_branch_protection` | integer | no | Determine if developers can push to master. Can take `0` _(not protected, both developers and masters can push new commits, force push or delete the branch)_, `1` _(partially protected, developers can push new commits, but cannot force push or delete the branch, masters can do anything)_ or `2` _(fully protected, developers cannot push new commits, force push or delete the branch, masters can do anything)_ as a parameter. Default is `1`. |
+| `twitter_sharing_enabled` | boolean | no | Allow users to share project creation on Twitter |
+| `restricted_visibility_levels` | array of integers | no | Selected levels cannot be used by non-admin users for projects or snippets. Can take `0` _(Private)_, `1` _(Internal)_ and `2` _(Public)_ as a parameter. Default is null which means there is no restriction. |
+| `max_attachment_size` | integer | no | Limit attachment size in MB |
+| `session_expire_delay` | integer | no | Session duration in minutes. GitLab restart is required to apply changes |
+| `default_project_visibility` | integer | no | What visibility level new projects receive. Can take `0` _(Private)_, `1` _(Internal)_ and `2` _(Public)_ as a parameter. Default is `0`.|
+| `default_snippet_visibility` | integer | no | What visibility level new snippets receive. Can take `0` _(Private)_, `1` _(Internal)_ and `2` _(Public)_ as a parameter. Default is `0`.|
+| `restricted_signup_domains` | array of strings | no | Force people to use only corporate emails for sign-up. Default is null, meaning there is no restriction. |
+| `user_oauth_applications` | boolean | no | Allow users to register any application to use GitLab as an OAuth provider |
+| `after_sign_out_path` | string | no | Where to redirect users after logout |
-- `default_projects_limit` - project limit per user
-- `signup_enabled` - enable registration
-- `signin_enabled` - enable login via GitLab account
-- `gravatar_enabled` - enable gravatar
-- `sign_in_text` - text on login page
-- `home_page_url` - redirect to this URL when not logged in
-- `default_branch_protection` - determine if developers can push to master
-- `twitter_sharing_enabled` - allow users to share project creation in twitter
-- `restricted_visibility_levels` - restrict certain visibility levels
-- `max_attachment_size` - limit attachment size
-- `session_expire_delay` - session lifetime
-- `default_project_visibility` - what visibility level new project receives
-- `default_snippet_visibility` - what visibility level new snippet receives
-- `restricted_signup_domains` - force people to use only corporate emails for signup
-- `user_oauth_applications` - allow users to create oauth applications
-- `after_sign_out_path` - where redirect user after logout
-
-All parameters are optional. You can send only one that you want to change.
+```bash
+curl -X PUT -H "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v3/application/settings?signup_enabled=false&default_project_visibility=1
+```
+Example response:
```json
{
@@ -79,7 +89,7 @@ All parameters are optional. You can send only one that you want to change.
"restricted_visibility_levels": [],
"max_attachment_size": 10,
"session_expire_delay": 10080,
- "default_project_visibility": 0,
+ "default_project_visibility": 1,
"default_snippet_visibility": 0,
"restricted_signup_domains": [],
"user_oauth_applications": true,
diff --git a/doc/api/system_hooks.md b/doc/api/system_hooks.md
index f9637d8a6c..dc036d7e27 100644
--- a/doc/api/system_hooks.md
+++ b/doc/api/system_hooks.md
@@ -1,40 +1,71 @@
# System hooks
-All methods require admin authorization.
+All methods require administrator authorization.
-The URL endpoint of the system hooks can be configured in [the admin area under hooks](/admin/hooks).
+The URL endpoint of the system hooks can also be configured using the UI in
+the admin area under **Hooks** (`/admin/hooks`).
+
+Read more about [system hooks](../system_hooks/system_hooks.md).
## List system hooks
-Get list of system hooks
+Get a list of all system hooks.
+
+---
```
GET /hooks
```
-Parameters:
+Example request:
-- **none**
+```bash
+curl -H "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v3/hooks
+```
+
+Example response:
```json
[
- {
- "id": 3,
- "url": "http://example.com/hook",
- "created_at": "2013-10-02T10:15:31Z"
- }
+ {
+ "id" : 1,
+ "url" : "https://gitlab.example.com/hook",
+ "created_at" : "2015-11-04T20:07:35.874Z"
+ }
]
```
-## Add new system hook hook
+## Add new system hook
+
+Add a new system hook.
+
+---
```
POST /hooks
```
-Parameters:
+| Attribute | Type | Required | Description |
+| --------- | ---- | -------- | ----------- |
+| `url` | string | yes | The hook URL |
-- `url` (required) - The hook URL
+Example request:
+
+```bash
+curl -X POST -H "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v3/hooks?url=https://gitlab.example.com/hook"
+```
+
+Example response:
+
+```json
+[
+ {
+ "id" : 2,
+ "url" : "https://gitlab.example.com/hook",
+ "created_at" : "2015-11-04T20:07:35.874Z"
+ }
+]
+```
## Test system hook
@@ -42,29 +73,68 @@ Parameters:
GET /hooks/:id
```
-Parameters:
+| Attribute | Type | Required | Description |
+| --------- | ---- | -------- | ----------- |
+| `id` | integer | yes | The ID of the hook |
-- `id` (required) - The ID of hook
+Example request:
+
+```bash
+curl -H "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v3/hooks/2
+```
+
+Example response:
```json
{
- "event_name": "project_create",
- "name": "Ruby",
- "path": "ruby",
- "project_id": 1,
- "owner_name": "Someone",
- "owner_email": "example@gitlabhq.com"
+ "project_id" : 1,
+ "owner_email" : "example@gitlabhq.com",
+ "owner_name" : "Someone",
+ "name" : "Ruby",
+ "path" : "ruby",
+ "event_name" : "project_create"
}
```
## Delete system hook
-Deletes a system hook. This is an idempotent API function and returns `200 OK` even if the hook is not available. If the hook is deleted it is also returned as JSON.
+Deletes a system hook. This is an idempotent API function and returns `200 OK`
+even if the hook is not available.
+
+If the hook is deleted, a JSON object is returned. An error is raised if the
+hook is not found.
+
+---
```
DELETE /hooks/:id
```
-Parameters:
+| Attribute | Type | Required | Description |
+| --------- | ---- | -------- | ----------- |
+| `id` | integer | yes | The ID of the hook |
-- `id` (required) - The ID of hook
+Example request:
+
+```bash
+curl -X DELETE -H "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v3/hooks/2
+```
+
+Example response:
+
+```json
+{
+ "note_events" : false,
+ "project_id" : null,
+ "enable_ssl_verification" : true,
+ "url" : "https://gitlab.example.com/hook",
+ "updated_at" : "2015-11-04T20:12:15.931Z",
+ "issues_events" : false,
+ "merge_requests_events" : false,
+ "created_at" : "2015-11-04T20:12:15.931Z",
+ "service_id" : null,
+ "id" : 2,
+ "push_events" : true,
+ "tag_push_events" : false
+}
+```
diff --git a/doc/ci/api/commits.md b/doc/ci/api/commits.md
index 4df7afc6c5..871de7abcc 100644
--- a/doc/ci/api/commits.md
+++ b/doc/ci/api/commits.md
@@ -1,5 +1,12 @@
# Commits API
+**DEPRECATED**
+
+Since GitLab 8.1, there is a new commit status API. Please see the [revised
+documentation](../../api/commits.md#commit-status).
+
+---
+
__Authentication is done by GitLab CI project token__
## Commits
diff --git a/doc/ci/api/projects.md b/doc/ci/api/projects.md
index 74a4c64d00..fe6b1c0135 100644
--- a/doc/ci/api/projects.md
+++ b/doc/ci/api/projects.md
@@ -18,7 +18,7 @@ GET /ci/projects
Returns:
```json
- [
+[
{
"id" : 271,
"name" : "gitlabhq",
diff --git a/doc/ci/api/runners.md b/doc/ci/api/runners.md
index c383dc4bcc..e9033aeacd 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/build_artifacts/README.md b/doc/ci/build_artifacts/README.md
index b02caa9edf..71db5aa5dc 100644
--- a/doc/ci/build_artifacts/README.md
+++ b/doc/ci/build_artifacts/README.md
@@ -11,15 +11,18 @@ Starting from GitLab 8.4 and GitLab Runner 1.0, the artifacts archive format
changed to `ZIP`, and it is now possible to browse its contents, with the added
ability of downloading the files separately.
+**Note:**
+The artifacts browser will be available only for new artifacts that are sent
+to GitLab using GitLab Runner version 1.0 and up. It will not be possible to
+browse old artifacts already uploaded to GitLab.
+
## Enabling build artifacts
-If you are searching for ways to use the artifacts feature, jump to
-[Defining artifacts in `.gitlab-ci.yml`](#defining-artifacts-in-gitlab-ciyml).
+_If you are searching for ways to use artifacts, jump to
+[Defining artifacts in `.gitlab-ci.yml`](#defining-artifacts-in-gitlab-ciyml)._
The artifacts feature is enabled by default in all GitLab installations.
-
-If by any chance you want to disable the artifacts feature on your GitLab
-instance, follow the steps below.
+To disable it site-wide, follow the steps below.
---
@@ -154,7 +157,7 @@ inside GitLab that make that possible.
1. While inside a specific build, you are presented with a download button
along with the one that browses the archive
-1. And finally, when browsing and archive you can see the download button at
+1. And finally, when browsing an archive you can see the download button at
the top right corner
---
diff --git a/doc/ci/docker/using_docker_images.md b/doc/ci/docker/using_docker_images.md
index 63fe840b36..bd748f1b98 100644
--- a/doc/ci/docker/using_docker_images.md
+++ b/doc/ci/docker/using_docker_images.md
@@ -270,7 +270,7 @@ This will forcefully (`-f`) remove the `build` container, the two service
containers as well as all volumes (`-v`) that were created with the container
creation.
-[Docker Fundamentals]: https://docs.docker.com/engine/introduction/understanding-docker/
+[Docker Fundamentals]: https://docs.docker.com/engine/understanding-docker/
[hub]: https://hub.docker.com/
[linking-containers]: https://docs.docker.com/engine/userguide/networking/default_network/dockerlinks/
[tutum/wordpress]: https://registry.hub.docker.com/u/tutum/wordpress/
diff --git a/doc/ci/examples/test-and-deploy-ruby-application-to-heroku.md b/doc/ci/examples/test-and-deploy-ruby-application-to-heroku.md
index e52e154746..c1bb47e429 100644
--- a/doc/ci/examples/test-and-deploy-ruby-application-to-heroku.md
+++ b/doc/ci/examples/test-and-deploy-ruby-application-to-heroku.md
@@ -56,12 +56,12 @@ gitlab-ci-multi-runner register \
--non-interactive \
--url "https://gitlab.com/ci/" \
--registration-token "PROJECT_REGISTRATION_TOKEN" \
- --description "ruby-2.1" \
+ --description "ruby-2.2" \
--executor "docker" \
- --docker-image ruby:2.1 \
+ --docker-image ruby:2.2 \
--docker-postgres latest
```
-With the command above, you create a runner that uses [ruby:2.1](https://registry.hub.docker.com/u/library/ruby/) image and uses [postgres](https://registry.hub.docker.com/u/library/postgres/) database.
+With the command above, you create a runner that uses [ruby:2.2](https://registry.hub.docker.com/u/library/ruby/) image and uses [postgres](https://registry.hub.docker.com/u/library/postgres/) database.
To access PostgreSQL database you need to connect to `host: postgres` as user `postgres` without password.
diff --git a/doc/ci/languages/php.md b/doc/ci/languages/php.md
index dacb67fa3f..aeadd6a448 100644
--- a/doc/ci/languages/php.md
+++ b/doc/ci/languages/php.md
@@ -12,7 +12,7 @@ configuration from the developer. To overcome this we will be using the
official [PHP docker image][php-hub] that can be found in Docker Hub.
This will allow us to test PHP projects against different versions of PHP.
-However, not everything is plug 'n' play, you still need to onfigure some
+However, not everything is plug 'n' play, you still need to configure some
things manually.
As with every build, you need to create a valid `.gitlab-ci.yml` describing the
@@ -97,7 +97,7 @@ image: php:5.6
before_script:
# Install dependencies
-- ci/docker_install.sh > /dev/null
+- bash ci/docker_install.sh > /dev/null
test:app:
script:
@@ -112,7 +112,7 @@ with a different docker image version and the runner will do the rest:
```yaml
before_script:
# Install dependencies
-- ci/docker_install.sh > /dev/null
+- bash ci/docker_install.sh > /dev/null
# We test PHP5.6
test:5.6:
diff --git a/doc/ci/quick_start/README.md b/doc/ci/quick_start/README.md
index a9b36139de..07e566e371 100644
--- a/doc/ci/quick_start/README.md
+++ b/doc/ci/quick_start/README.md
@@ -36,13 +36,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
@@ -168,7 +168,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 +184,23 @@ 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:
+
+```
+http://example.gitlab.com/namespace/project/badges/branch/build.svg
+```
+
## Next steps
Awesome! You started using CI in GitLab!
diff --git a/doc/ci/variables/README.md b/doc/ci/variables/README.md
index b99ea25a3f..9e89e6e395 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 |
@@ -56,7 +56,7 @@ export CI_SERVER_VERSION=""
```
### YAML-defined variables
-**This feature requires GitLab Runner 0.5.0 or higher**
+**This feature requires GitLab Runner 0.5.0 or higher and GitLab CI 7.14 or higher **
GitLab CI allows you to add to `.gitlab-ci.yml` variables that are set in build environment.
The variables are stored in repository and are meant to store non-sensitive project configuration, ie. RAILS_ENV or DATABASE_URL.
@@ -77,9 +77,12 @@ More information about Docker integration can be found in [Using Docker Images](
GitLab CI allows you to define per-project **Secure Variables** that are set in build environment.
The secure variables are stored out of the repository (the `.gitlab-ci.yml`).
-These variables are securely stored in GitLab CI database and are hidden in the build log.
+The variables are securely passed to GitLab Runner and are available in build environment.
It's desired method to use them for storing passwords, secret keys or whatever you want.
+**The value of the variable can be shown in build log if explicitly asked to do so.**
+If your project is public or internal you can make the builds private.
+
Secure Variables can added by going to `Project > Variables > Add Variable`.
They will be available for all subsequent builds.
diff --git a/doc/ci/yaml/README.md b/doc/ci/yaml/README.md
index 3b594df659..0edb56dc20 100644
--- a/doc/ci/yaml/README.md
+++ b/doc/ci/yaml/README.md
@@ -159,50 +159,55 @@ The `key` directive allows you to define the affinity of caching
between jobs, allowing to have a single cache for all jobs,
cache per-job, cache per-branch or any other way you deem proper.
-This allows you to fine tune caching, allowing you to cache data between different jobs or even different branches.
-The `cache:key` variable can use any of the [predefined variables](../variables/README.md):
+This allows you to fine tune caching, allowing you to cache data between
+different jobs or even different branches.
-Example configurations:
+The `cache:key` variable can use any of the [predefined variables](../variables/README.md).
+
+---
+
+**Example configurations**
To enable per-job caching:
- ```yaml
- cache:
- key: "$CI_BUILD_NAME"
- untracked: true
- ```
+```yaml
+cache:
+ key: "$CI_BUILD_NAME"
+ untracked: true
+```
To enable per-branch caching:
- ```yaml
- cache:
- key: "$CI_BUILD_REF_NAME"
- untracked: true
- ```
+```yaml
+cache:
+ key: "$CI_BUILD_REF_NAME"
+ untracked: true
+```
To enable per-job and per-branch caching:
- ```yaml
- cache:
- key: "$CI_BUILD_NAME/$CI_BUILD_REF_NAME"
- untracked: true
- ```
+```yaml
+cache:
+ key: "$CI_BUILD_NAME/$CI_BUILD_REF_NAME"
+ untracked: true
+```
To enable per-branch and per-stage caching:
- ```yaml
- cache:
- key: "$CI_BUILD_STAGE/$CI_BUILD_REF_NAME"
- untracked: true
- ```
+```yaml
+cache:
+ key: "$CI_BUILD_STAGE/$CI_BUILD_REF_NAME"
+ untracked: true
+```
-If you use **Windows Batch** to run your shell scripts you need to replace the `$` with `%`:
+If you use **Windows Batch** to run your shell scripts you need to replace
+`$` with `%`:
- ```yaml
- cache:
- key: "%CI_BUILD_STAGE%/%CI_BUILD_REF_NAME%"
- untracked: true
- ```
+```yaml
+cache:
+ key: "%CI_BUILD_STAGE%/%CI_BUILD_REF_NAME%"
+ untracked: true
+```
## Jobs
@@ -388,8 +393,12 @@ The above script will:
### artifacts
-_**Note:** Introduced in GitLab Runner v0.7.0. Also, the Windows shell executor
- does not currently support artifact uploads._
+_**Note:** Introduced in GitLab Runner v0.7.0 for non-Windows platforms._
+
+_**Note:** Limited Windows support was added in GitLab Runner v.1.0.0.
+Currently not all executors are supported._
+
+_**Note:** Build artifacts are only collected for successful builds._
`artifacts` is used to specify list of files and directories which should be
attached to build after success. Below are some examples.
@@ -419,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
diff --git a/doc/customization/branded_login_page.md b/doc/customization/branded_login_page.md
new file mode 100644
index 0000000000..d4d9f5f7b5
--- /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 0000000000..6bce1f0a28
Binary files /dev/null and b/doc/customization/branded_login_page/appearance.png 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 0000000000..d6020b029a
Binary files /dev/null and b/doc/customization/branded_login_page/custom_sign_in.png 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 0000000000..795c7954d8
Binary files /dev/null and b/doc/customization/branded_login_page/default_login_page.png differ
diff --git a/doc/customization/issue_closing.md b/doc/customization/issue_closing.md
index 00edfc97ed..194b8e0029 100644
--- a/doc/customization/issue_closing.md
+++ b/doc/customization/issue_closing.md
@@ -16,7 +16,7 @@ Here, `%{issue_ref}` is a complex regular expression defined inside GitLab, that
For example:
```
-git commit -m "Awesome commit message (Fix #20, Fixes #21 and Closes group/otherproject#2). This commit is also related to #17 and fixes #18, #19 and https://gitlab.example.com/group/otherproject/issues/23."
+git commit -m "Awesome commit message (Fix #20, Fixes #21 and Closes group/otherproject#22). This commit is also related to #17 and fixes #18, #19 and https://gitlab.example.com/group/otherproject/issues/23."
```
will close `#18`, `#19`, `#20`, and `#21` in the project this commit is pushed to, as well as `#22` and `#23` in group/otherproject. `#17` won't be closed as it does not match the pattern. It also works with multiline commit messages.
diff --git a/doc/customization/welcome_message.md b/doc/customization/welcome_message.md
index e993230bb8..a0cb234bea 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/ci_setup.md b/doc/development/ci_setup.md
index f9b4886818..6776d9b083 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.1.2
+- 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 caaa4032db..96d1dffbc5 100644
--- a/doc/development/doc_styleguide.md
+++ b/doc/development/doc_styleguide.md
@@ -120,6 +120,17 @@ Inside the document:
`http://doc.gitlab.com/ce/administration/restart_gitlab.html`.
Replace `reconfigure` with `restart` where appropriate.
+## Installation guide
+
+- **Ruby:**
+ In [step 2 of the installation guide](../install/installation.md#2-ruby),
+ we install Ruby from source. Whenever there is a new version that needs to
+ be updated, remember to change it throughout the codeblock and also replace
+ the sha256sum (it can be found in the [downloads page][ruby-dl] of the Ruby
+ website).
+
+[ruby-dl]: https://www.ruby-lang.org/en/downloads/ "Ruby download website"
+
## API
Here is a list of must-have items. Use them in the exact order that appears
diff --git a/doc/development/migration_style_guide.md b/doc/development/migration_style_guide.md
index 4fa1961fde..28dedf3978 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/install/installation.md b/doc/install/installation.md
index bb3fef9388..446d202342 100644
--- a/doc/install/installation.md
+++ b/doc/install/installation.md
@@ -76,7 +76,7 @@ Make sure you have the right version of Git installed
# Install Git
sudo apt-get install -y git-core
- # Make sure Git is version 1.7.10 or higher, for example 1.7.12 or 2.0.0
+ # Make sure Git is version 2.7.4 or higher
git --version
Is the system packaged Git too old? Remove it and compile from source.
@@ -89,8 +89,9 @@ Is the system packaged Git too old? Remove it and compile from source.
# Download and compile from source
cd /tmp
- curl -L --progress https://www.kernel.org/pub/software/scm/git/git-2.4.3.tar.gz | tar xz
- cd git-2.4.3/
+ curl -O --progress https://www.kernel.org/pub/software/scm/git/git-2.7.4.tar.gz
+ echo '7104c4f5d948a75b499a954524cb281fe30c6649d8abe20982936f75ec1f275b git-2.7.4.tar.gz' | shasum -a256 -c - && tar -xzf git-2.7.4.tar.gz
+ cd git-2.7.4/
./configure
make prefix=/usr/local all
@@ -107,18 +108,25 @@ Then select 'Internet Site' and press enter to confirm the hostname.
## 2. Ruby
-The use of Ruby version managers such as [RVM](https://rvm.io/), [rbenv](https://github.com/sstephenson/rbenv) or [chruby](https://github.com/postmodern/chruby) with GitLab in production frequently leads to hard to diagnose problems. For example, GitLab Shell is called from OpenSSH and having a version manager can prevent pushing and pulling over SSH. Version managers are not supported and we strongly advise everyone to follow the instructions below to use a system Ruby.
+_**Note:** The current supported Ruby version is 2.1.x. Ruby 2.2 and 2.3 are
+currently not supported._
-Remove the old Ruby 1.8 if present
+The use of Ruby version managers such as [RVM], [rbenv] or [chruby] with GitLab
+in production, frequently leads to hard to diagnose problems. For example,
+GitLab Shell is called from OpenSSH, and having a version manager can prevent
+pushing and pulling over SSH. Version managers are not supported and we strongly
+advise everyone to follow the instructions below to use a system Ruby.
+
+Remove the old Ruby 1.8 if present:
sudo apt-get remove ruby1.8
Download Ruby and compile it:
mkdir /tmp/ruby && cd /tmp/ruby
- curl -O --progress https://cache.ruby-lang.org/pub/ruby/2.1/ruby-2.1.7.tar.gz
- echo 'e2e195a4a58133e3ad33b955c829bb536fa3c075 ruby-2.1.7.tar.gz' | shasum -c - && tar xzf ruby-2.1.7.tar.gz
- cd ruby-2.1.7
+ 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
@@ -136,7 +144,7 @@ use 64-bit Linux. You can find downloads for other platforms at the [Go download
page](https://golang.org/dl).
curl -O --progress https://storage.googleapis.com/golang/go1.5.3.linux-amd64.tar.gz
- echo '43afe0c5017e502630b1aea4d44b8a7f059bf60d7f29dfd58db454d4e4e0ae53 go1.5.3.linux-amd64.tar.gz' | shasum -c - && \
+ echo '43afe0c5017e502630b1aea4d44b8a7f059bf60d7f29dfd58db454d4e4e0ae53 go1.5.3.linux-amd64.tar.gz' | shasum -a256 -c - && \
sudo tar -C /usr/local -xzf go1.5.3.linux-amd64.tar.gz
sudo ln -sf /usr/local/go/bin/{go,godoc,gofmt} /usr/local/bin/
rm go1.5.3.linux-amd64.tar.gz
@@ -175,25 +183,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
@@ -217,7 +220,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
@@ -231,9 +234,9 @@ sudo usermod -aG redis git
### Clone the Source
# Clone GitLab repository
- sudo -u git -H git clone https://gitlab.com/gitlab-org/gitlab-ce.git -b 8-4-stable gitlab
+ sudo -u git -H git clone https://gitlab.com/gitlab-org/gitlab-ce.git -b 8-5-stable gitlab
-**Note:** You can change `8-4-stable` to `master` if you want the *bleeding edge* version, but never install master on a production server!
+**Note:** You can change `8-5-stable` to `master` if you want the *bleeding edge* version, but never install master on a production server!
### Configure It
@@ -260,8 +263,12 @@ sudo usermod -aG redis git
sudo chmod -R u+rwX tmp/pids/
sudo chmod -R u+rwX tmp/sockets/
- # Make sure GitLab can write to the public/uploads/ directory
- sudo chmod -R u+rwX public/uploads
+ # Create the public/uploads/ directory
+ sudo -u git -H mkdir 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/
@@ -348,7 +355,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.2
+ sudo -u git -H git checkout 0.6.4
sudo -u git -H make
### Initialize Database and Activate Advanced Features
@@ -363,9 +370,9 @@ GitLab Shell is an SSH access and repository management software developed speci
# When done you see 'Administrator account created:'
-**Note:** You can set the Administrator/root password by supplying it in environmental variable `GITLAB_ROOT_PASSWORD` as seen below. If you don't set the password (and it is set to the default one) please wait with exposing GitLab to the public internet until the installation is done and you've logged into the server the first time. During the first login you'll be forced to change the default password.
+**Note:** You can set the Administrator/root password and e-mail by supplying them in environmental variables, `GITLAB_ROOT_PASSWORD` and `GITLAB_ROOT_EMAIL` respectively, as seen below. If you don't set the password (and it is set to the default one) please wait with exposing GitLab to the public internet until the installation is done and you've logged into the server the first time. During the first login you'll be forced to change the default password.
- sudo -u git -H bundle exec rake gitlab:setup RAILS_ENV=production GITLAB_ROOT_PASSWORD=yourpassword
+ sudo -u git -H bundle exec rake gitlab:setup RAILS_ENV=production GITLAB_ROOT_PASSWORD=yourpassword GITLAB_ROOT_EMAIL=youremail
### Secure secrets.yml
@@ -474,6 +481,11 @@ You can use `sudo service gitlab start` and `sudo service gitlab stop` to start
## Advanced Setup Tips
+### Relative URL support
+
+See the [Relative URL documentation](relative_url.md) for more information on
+how to configure GitLab with a relative URL.
+
### Using HTTPS
To use GitLab with HTTPS:
@@ -555,3 +567,7 @@ this is likely due to an outdated Nginx or Apache configuration, or a missing or
misconfigured gitlab-workhorse instance. Double-check that you've
[installed Go](#3-go), [installed gitlab-workhorse](#install-gitlab-workhorse),
and correctly [configured Nginx](#site-configuration).
+
+[RVM]: https://rvm.io/ "RVM Homepage"
+[rbenv]: https://github.com/sstephenson/rbenv "rbenv on GitHub"
+[chruby]: https://github.com/postmodern/chruby "chruby on GitHub"
diff --git a/doc/install/redis.md b/doc/install/redis.md
new file mode 100644
index 0000000000..4075e6283d
--- /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
new file mode 100644
index 0000000000..0245febfcd
--- /dev/null
+++ b/doc/install/relative_url.md
@@ -0,0 +1,136 @@
+## Install GitLab under a relative URL
+
+_**Note:**
+This document describes how to run GitLab under a relative URL for installations
+from source. If you are using an Omnibus package,
+[the steps are different][omnibus-rel]. Use this guide along with the
+[installation guide](installation.md) if you are installing GitLab for the
+first time._
+
+---
+
+While it is recommended to install GitLab on its own (sub)domain, sometimes
+this is not possible due to a variety of reasons. In that case, GitLab can also
+be installed under a relative URL, for example `https://example.com/gitlab`.
+
+There is no limit to how deeply nested the relative URL can be. For example you
+could serve GitLab under `/foo/bar/gitlab/git` without any issues.
+
+Note that by changing the URL on an existing GitLab installation, all remote
+URLs will change, so you'll have to manually edit them in any local repository
+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/initializers/relative_url.rb`
+- `/home/git/gitlab/config/gitlab.yml`
+- `/home/git/gitlab/config/unicorn.rb`
+- `/home/git/gitlab-shell/config.yml`
+- `/etc/default/gitlab`
+
+After all the changes you need to recompile the assets and [restart GitLab].
+
+### Relative URL requirements
+
+If you configure GitLab with a relative URL, the assets (JavaScript, CSS, fonts,
+images, etc.) will need to be recompiled, which is a task which consumes a lot
+of CPU and memory resources. To avoid out-of-memory errors, you should have at
+least 2GB of RAM available on your system, while we recommend 4GB RAM, and 4 or
+8 CPU cores.
+
+See the [requirements](requirements.md) document for more information.
+
+### Enable relative URL in GitLab
+
+_**Note:**
+Do not make any changes to your web server configuration file regarding
+relative URL. The relative URL support is implemented by GitLab Workhorse._
+
+---
+
+Before following the steps below to enable relative URL in GitLab, some
+assumptions are made:
+
+- GitLab is served under `/gitlab`
+- The directory under which GitLab is installed is `/home/git/`
+
+Make sure to follow all steps below:
+
+1. (Optional) If you run short on resources, you can temporarily free up some
+ memory by shutting down the GitLab service with the following command:
+
+ ```shell
+ sudo service gitlab stop
+ ```
+
+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"
+ ```
+
+1. Edit `/home/git/gitlab/config/gitlab.yml` and uncomment/change the
+ following line:
+
+ ```yaml
+ relative_url_root: /gitlab
+ ```
+
+1. Edit `/home/git/gitlab/config/unicorn.rb` and uncomment/change the
+ following line:
+
+ ```ruby
+ ENV['RAILS_RELATIVE_URL_ROOT'] = "/gitlab"
+ ```
+
+1. Edit `/home/git/gitlab-shell/config.yml` and append the relative path to
+ the following line:
+
+ ```yaml
+ gitlab_url: http://127.0.0.1/gitlab
+ ```
+
+1. Make sure you have copied the supplied init script and the defaults file
+ as stated in the [installation guide](installation.md#install-init-script).
+ Then, edit `/etc/default/gitlab` and set in `gitlab_workhorse_options` the
+ `-authBackend` setting to read like:
+
+ ```shell
+ -authBackend http://127.0.0.1:8080/gitlab
+ ```
+
+ **Note:**
+ If you are using a custom init script, make sure to edit the above
+ gitlab-workhorse setting as needed.
+
+1. After all the above changes recompile the assets. This is an important task
+ and will take some time to complete depending on the server resources:
+
+ ```
+ cd /home/git/gitlab
+ sudo -u git -H bundle exec rake assets:clean assets:precompile RAILS_ENV=production
+ ```
+
+1. [Restart GitLab][] for the changes to take effect.
+
+### Disable relative URL in GitLab
+
+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 c0ccdd3745..3cab677fdc 100644
--- a/doc/install/requirements.md
+++ b/doc/install/requirements.md
@@ -32,15 +32,17 @@ Please consider using a virtual machine to run GitLab.
## Ruby versions
-GitLab requires Ruby (MRI) 2.1
+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 needs several Gems that have native extensions.
+We love [JRuby](http://jruby.org/) and [Rubinius](http://rubini.us/) but GitLab
+needs several Gems that have native extensions.
## Hardware requirements
### Storage
-The necessary hard drive space largely depends on the size of the repos you want to store in GitLab but as a *rule of thumb* you should have at least as much free space as all your repos combined take up.
+The necessary hard drive space largely depends on the size of the repos you want to store in GitLab but as a *rule of thumb* you should have at least as much free space as all your repos combined take up.
If you want to be flexible about growing your hard drive space in the future consider mounting it using LVM so you can add more hard drives when you need them.
@@ -64,7 +66,7 @@ If you have enough RAM memory and a recent CPU the speed of GitLab is mainly lim
You need at least 2GB of addressable memory (RAM + swap) to install and use GitLab!
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 advise.
+- 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
- 4GB RAM supports up to 1,000 users
@@ -109,4 +111,4 @@ On a very active server (10,000 active users) the Sidekiq process can use 1GB+ o
- Firefox (Latest released version and [latest ESR version](https://www.mozilla.org/en-US/firefox/organizations/))
- Safari 7+ (known problem: required fields in html5 do not work)
- Opera (Latest released version)
-- Internet Explorer (IE) 10+ but please make sure that you have the `Compatibility View` mode disabled.
\ No newline at end of file
+- Internet Explorer (IE) 10+ but please make sure that you have the `Compatibility View` mode disabled.
diff --git a/doc/integration/README.md b/doc/integration/README.md
index 5edac746c7..281eea8363 100644
--- a/doc/integration/README.md
+++ b/doc/integration/README.md
@@ -5,16 +5,17 @@ trackers and external authentication.
See the documentation below for details on how to configure these services.
-- [Jira](jira.md) Integrate with the JIRA issue tracker
+- [Jira](../project_services/jira.md) Integrate with the JIRA issue tracker
- [External issue tracker](external-issue-tracker.md) Redmine, JIRA, etc.
- [LDAP](ldap.md) Set up sign in via LDAP
-- [OmniAuth](omniauth.md) Sign in via Twitter, GitHub, GitLab, and Google via OAuth.
+- [OmniAuth](omniauth.md) Sign in via Twitter, GitHub, GitLab.com, Google, Bitbucket, Facebook, Shibboleth, SAML, Crowd and Azure
- [SAML](saml.md) Configure GitLab as a SAML 2.0 Service Provider
- [CAS](cas.md) Configure GitLab to sign in using CAS
- [Slack](slack.md) Integrate with the Slack chat service
- [OAuth2 provider](oauth_provider.md) OAuth2 application creation
- [Gmail actions buttons](gmail_action_buttons_for_gitlab.md) Adds GitLab actions to messages
- [reCAPTCHA](recaptcha.md) Configure GitLab to use Google reCAPTCHA for new users
+- [Akismet](akismet.md) Configure Akismet to stop spam
GitLab Enterprise Edition contains [advanced Jenkins support][jenkins].
diff --git a/doc/integration/akismet.md b/doc/integration/akismet.md
new file mode 100644
index 0000000000..5cc09bd536
--- /dev/null
+++ b/doc/integration/akismet.md
@@ -0,0 +1,30 @@
+# Akismet
+
+GitLab leverages [Akismet](http://akismet.com) to protect against spam. Currently
+GitLab uses Akismet to prevent users who are not members of a project from
+creating spam via the GitLab API. Detected spam will be rejected, and
+an entry in the "Spam Log" section in the Admin page will be created.
+
+Privacy note: GitLab submits the user's IP and user agent to Akismet. Note that
+adding a user to a project will disable the Akismet check and prevent this
+from happening.
+
+## Configuration
+
+To use Akismet:
+
+1. Go to the URL: https://akismet.com/account/
+
+2. Sign-in or create a new account.
+
+3. Click on "Show" to reveal the API key.
+
+4. Go to Applications Settings on Admin Area (`admin/application_settings`)
+
+5. Check the `Enable Akismet` checkbox
+
+6. Fill in the API key from step 3.
+
+7. Save the configuration.
+
+![Screenshot of Akismet settings](img/akismet_settings.png)
diff --git a/doc/integration/external-issue-tracker.md b/doc/integration/external-issue-tracker.md
index 3543a67dd4..a2d7e922aa 100644
--- a/doc/integration/external-issue-tracker.md
+++ b/doc/integration/external-issue-tracker.md
@@ -19,7 +19,7 @@ To enable an external issue tracker you must configure the appropriate **Service
Visit the links below for details:
- [Redmine](../project_services/redmine.md)
-- [Jira](jira.md)
+- [Jira](../project_services/jira.md)
### Service Template
diff --git a/doc/integration/facebook.md b/doc/integration/facebook.md
index bc1f167308..77bb75cbfc 100644
--- a/doc/integration/facebook.md
+++ b/doc/integration/facebook.md
@@ -19,7 +19,7 @@ something else descriptive.
1. Enter the address of your GitLab installation at the bottom of the package
- ![Facebook Website URL](facebook_website_url.png)
+ ![Facebook Website URL](img/facebook_website_url.png)
1. Choose "Next"
@@ -29,7 +29,7 @@ something else descriptive.
1. Fill in a contact email for your app
- ![Facebook App Settings](facebook_app_settings.png)
+ ![Facebook App Settings](img/facebook_app_settings.png)
1. Choose "Save Changes"
@@ -45,7 +45,7 @@ something else descriptive.
1. You should now see an app key and app secret (see screenshot). Keep this page open as you continue configuration.
- ![Facebook API Keys](facebook_api_keys.png)
+ ![Facebook API Keys](img/facebook_api_keys.png)
1. On your GitLab server, open the configuration file.
diff --git a/doc/integration/github.md b/doc/integration/github.md
index a789d2c814..886784a27c 100644
--- a/doc/integration/github.md
+++ b/doc/integration/github.md
@@ -22,7 +22,7 @@ GitHub will generate an application ID and secret key for you to use.
1. You should now see a Client ID and Client Secret near the top right of the page (see screenshot).
Keep this page open as you continue configuration.
- ![GitHub app](github_app.png)
+ ![GitHub app](img/github_app.png)
1. On your GitLab server, open the configuration file.
diff --git a/doc/integration/gitlab.md b/doc/integration/gitlab.md
index 80e3c0142a..b215cc7c60 100644
--- a/doc/integration/gitlab.md
+++ b/doc/integration/gitlab.md
@@ -28,7 +28,7 @@ GitLab.com will generate an application ID and secret key for you to use.
1. You should now see a Client ID and Client Secret near the top right of the page (see screenshot).
Keep this page open as you continue configuration.
- ![GitLab app](gitlab_app.png)
+ ![GitLab app](img/gitlab_app.png)
1. On your GitLab server, open the configuration file.
diff --git a/doc/integration/gmail_action_buttons_for_gitlab.md b/doc/integration/gmail_action_buttons_for_gitlab.md
index de45f25ad6..05a91d9bef 100644
--- a/doc/integration/gmail_action_buttons_for_gitlab.md
+++ b/doc/integration/gmail_action_buttons_for_gitlab.md
@@ -4,7 +4,7 @@ GitLab supports [Google actions in email](https://developers.google.com/gmail/ma
If correctly setup, emails that require an action will be marked in Gmail.
-![gmail_actions_button.png](gmail_actions_button.png)
+![gmail_actions_button.png](img/gmail_action_buttons_for_gitlab.png)
To get this functioning, you need to be registered with Google.
[See how to register with Google in this document.](https://developers.google.com/gmail/markup/registering-with-google)
diff --git a/doc/integration/google.md b/doc/integration/google.md
index 91e9b2495c..f9a20dd840 100644
--- a/doc/integration/google.md
+++ b/doc/integration/google.md
@@ -25,7 +25,7 @@ To enable the Google OAuth2 OmniAuth provider you must register your application
- Application type: "Web Application"
- Authorized JavaScript origins: This isn't really used by GitLab but go ahead and put 'https://gitlab.example.com' here.
- Authorized redirect URI: 'https://gitlab.example.com/users/auth/google_oauth2/callback'
-1. Under the heading "Client ID for web application" you should see a Client ID and Client secret (see screenshot). Keep this page open as you continue configuration. ![Google app](google_app.png)
+1. Under the heading "Client ID for web application" you should see a Client ID and Client secret (see screenshot). Keep this page open as you continue configuration. ![Google app](img/google_app.png)
1. On your GitLab server, open the configuration file.
diff --git a/doc/integration/img/akismet_settings.png b/doc/integration/img/akismet_settings.png
new file mode 100644
index 0000000000..ccdd3adb1c
Binary files /dev/null and b/doc/integration/img/akismet_settings.png differ
diff --git a/doc/integration/facebook_api_keys.png b/doc/integration/img/facebook_api_keys.png
similarity index 100%
rename from doc/integration/facebook_api_keys.png
rename to doc/integration/img/facebook_api_keys.png
diff --git a/doc/integration/facebook_app_settings.png b/doc/integration/img/facebook_app_settings.png
similarity index 100%
rename from doc/integration/facebook_app_settings.png
rename to doc/integration/img/facebook_app_settings.png
diff --git a/doc/integration/facebook_website_url.png b/doc/integration/img/facebook_website_url.png
similarity index 100%
rename from doc/integration/facebook_website_url.png
rename to doc/integration/img/facebook_website_url.png
diff --git a/doc/integration/github_app.png b/doc/integration/img/github_app.png
similarity index 100%
rename from doc/integration/github_app.png
rename to doc/integration/img/github_app.png
diff --git a/doc/integration/gitlab_app.png b/doc/integration/img/gitlab_app.png
similarity index 100%
rename from doc/integration/gitlab_app.png
rename to doc/integration/img/gitlab_app.png
diff --git a/doc/integration/gmail_actions_button.png b/doc/integration/img/gmail_action_buttons_for_gitlab.png
similarity index 100%
rename from doc/integration/gmail_actions_button.png
rename to doc/integration/img/gmail_action_buttons_for_gitlab.png
diff --git a/doc/integration/google_app.png b/doc/integration/img/google_app.png
similarity index 100%
rename from doc/integration/google_app.png
rename to doc/integration/img/google_app.png
diff --git a/doc/integration/img/oauth_provider_admin_application.png b/doc/integration/img/oauth_provider_admin_application.png
new file mode 100644
index 0000000000..a2d8e14c12
Binary files /dev/null and b/doc/integration/img/oauth_provider_admin_application.png differ
diff --git a/doc/integration/img/oauth_provider_application_form.png b/doc/integration/img/oauth_provider_application_form.png
new file mode 100644
index 0000000000..3a676b2239
Binary files /dev/null and b/doc/integration/img/oauth_provider_application_form.png differ
diff --git a/doc/integration/img/oauth_provider_application_id_secret.png b/doc/integration/img/oauth_provider_application_id_secret.png
new file mode 100644
index 0000000000..6d68df001a
Binary files /dev/null and b/doc/integration/img/oauth_provider_application_id_secret.png differ
diff --git a/doc/integration/img/oauth_provider_authorized_application.png b/doc/integration/img/oauth_provider_authorized_application.png
new file mode 100644
index 0000000000..efc3b807d7
Binary files /dev/null and b/doc/integration/img/oauth_provider_authorized_application.png differ
diff --git a/doc/integration/img/oauth_provider_user_wide_applications.png b/doc/integration/img/oauth_provider_user_wide_applications.png
new file mode 100644
index 0000000000..45ad8a6d46
Binary files /dev/null and b/doc/integration/img/oauth_provider_user_wide_applications.png differ
diff --git a/doc/integration/twitter_app_api_keys.png b/doc/integration/img/twitter_app_api_keys.png
similarity index 100%
rename from doc/integration/twitter_app_api_keys.png
rename to doc/integration/img/twitter_app_api_keys.png
diff --git a/doc/integration/twitter_app_details.png b/doc/integration/img/twitter_app_details.png
similarity index 100%
rename from doc/integration/twitter_app_details.png
rename to doc/integration/img/twitter_app_details.png
diff --git a/doc/integration/jira-integration-points.png b/doc/integration/jira-integration-points.png
deleted file mode 100644
index 0692a7b458..0000000000
Binary files a/doc/integration/jira-integration-points.png and /dev/null differ
diff --git a/doc/integration/jira.md b/doc/integration/jira.md
index de574d5341..78aa663411 100644
--- a/doc/integration/jira.md
+++ b/doc/integration/jira.md
@@ -1,149 +1,3 @@
-# GitLab Jira integration
+# GitLab JIRA integration
-GitLab can be configured to interact with Jira. Configuration happens via
-username and password. Connecting to a Jira server via CAS is not possible.
-
-Each project can be configured to connect to a different Jira instance, see the
-[configuration](#configuration) section. If you have one Jira instance you can
-pre-fill the settings page with a default template. To configure the template
-see the [Services Templates][services-templates] document.
-
-Once the project is connected to Jira, you can reference and close the issues
-in Jira directly from GitLab.
-
-## Table of Contents
-
-* [Referencing Jira Issues from GitLab](#referencing-jira-issues)
-* [Closing Jira Issues from GitLab](#closing-jira-issues)
-* [Configuration](#configuration)
-
-### Referencing Jira Issues
-
-When GitLab project has Jira issue tracker configured and enabled, mentioning
-Jira issue in GitLab will automatically add a comment in Jira issue with the
-link back to GitLab. This means that in comments in merge requests and commits
-referencing an issue, eg. `PROJECT-7`, will add a comment in Jira issue in the
-format:
-
-```
- USER mentioned this issue in LINK_TO_THE_MENTION
-```
-
-* `USER` A user that mentioned the issue. This is the link to the user profile in GitLab.
-* `LINK_TO_THE_MENTION` Link to the origin of mention with a name of the entity where Jira issue was mentioned.
-Can be commit or merge request.
-
-![example of mentioning or closing the Jira issue](img/jira_issue_reference.png)
-
----
-
-### Closing Jira Issues
-
-Jira issues can be closed directly from GitLab by using trigger words, eg.
-`Resolves PROJECT-1`, `Closes PROJECT-1` or `Fixes PROJECT-1`, in commits and
-merge requests. When a commit which contains the trigger word in the commit
-message is pushed, GitLab will add a comment in the mentioned Jira issue.
-
-For example, for project named `PROJECT` in Jira, we implemented a new feature
-and created a merge request in GitLab.
-
-This feature was requested in Jira issue `PROJECT-7`. Merge request in GitLab
-contains the improvement and in merge request description we say that this
-merge request `Closes PROJECT-7` issue.
-
-Once this merge request is merged, the Jira issue will be automatically closed
-with a link to the commit that resolved the issue.
-
-![A Git commit that causes the Jira issue to be closed](img/jira_merge_request_close.png)
-
----
-
-![The GitLab integration user leaves a comment on Jira](img/jira_service_close_issue.png)
-
----
-
-## Configuration
-
-### Configuring JIRA
-
-We need to create a user in JIRA which will have access to all projects that
-need to integrate with GitLab. Login to your JIRA instance as admin and under
-Administration go to User Management and create a new user.
-
-As an example, we'll create a user named `gitlab` and add it to `jira-developers`
-group.
-
-**It is important that the user `gitlab` has write-access to projects in JIRA**
-
-### Configuring GitLab
-
-JIRA configuration in GitLab is done via a project's **Services**.
-
-#### GitLab 7.8 and up with JIRA v6.x
-
-See next section.
-
-#### GitLab 7.8 and up
-
-_The currently supported JIRA versions are v6.x and v7.x._
-
-To enable JIRA integration in a project, navigate to the project's
-**Settings > Services > JIRA**.
-
-Fill in the required details on the page as described in the table below.
-
-| Field | Description |
-| ----- | ----------- |
-| `description` | A name for the issue tracker (to differentiate between instances, for instance). |
-| `project url` | The URL to the JIRA project which is being linked to this GitLab project. |
-| `issues url` | The URL to the JIRA project issues overview for the project that is linked to this GitLab project. |
-| `new issue url` | This is the URL to create a new issue in JIRA for the project linked to this GitLab project. |
-| `api url` | The base URL of the JIRA API. It may be omitted, in which case GitLab will automatically use API version `2` based on the `project url`, i.e. `https://jira.example.com/rest/api/2`. |
-| `username` | The username of the user created in [configuring JIRA step](#configuring-jira). |
-| `password` |The password of the user created in [configuring JIRA step](#configuring-jira). |
-| `Jira issue transition` | This is the ID of a transition that moves issues to a closed state. You can find this number under JIRA workflow administration ([see screenshot](img/jira_workflow_screenshot.png)). By default, this ID is `2` (in the example image, this is `2` as well) |
-
-After saving the configuration, your GitLab project will be able to interact
-with the linked JIRA project.
-
-![Jira service page](img/jira_service_page.png)
-
----
-
-#### GitLab 6.x-7.7 with JIRA v6.x
-
-_**Note:** GitLab versions 7.8 and up contain various integration improvements.
-We strongly recommend upgrading._
-
-In `gitlab.yml` enable the JIRA issue tracker section by
-[uncommenting these lines][jira-gitlab-yml]. This will make sure that all
-issues within GitLab are pointing to the JIRA issue tracker.
-
-After you set this, you will be able to close issues in JIRA by a commit in
-GitLab.
-
-Go to your project's **Settings** page and fill in the project name for the
-JIRA project:
-
-![Set the JIRA project name in GitLab to 'NEW'](img/jira_project_name.png)
-
----
-
-You can also enable the JIRA service that will allow you to interact with JIRA
-issues. Go to the **Settings > Services > JIRA** and:
-
-1. Tick the active check box to enable the service
-1. Supply the URL to JIRA server, for example http://jira.example.com
-1. Supply the username of a user we created under `Configuring JIRA` section,
- for example `gitlab`
-1. Supply the password of the user
-1. Optional: supply the JIRA API version, default is version `2`
-1. Optional: supply the JIRA issue transition ID (issue transition to closed).
- This is dependent on JIRA settings, default is `2`
-1. Hit save
-
-
-![Jira services page](img/jira_service.png)
-
-[services-templates]: ../project_services/services_templates.md
-[jira-gitlab-yml]: https://gitlab.com/subscribers/gitlab-ee/blob/6-8-stable-ee/config/gitlab.yml.example#L111-115
+This document was moved under [project_services/jira](../project_services/jira.md).
diff --git a/doc/integration/oauth_provider.md b/doc/integration/oauth_provider.md
index 192c321f71..5f8bb57365 100644
--- a/doc/integration/oauth_provider.md
+++ b/doc/integration/oauth_provider.md
@@ -1,35 +1,80 @@
-## GitLab as OAuth2 authentication service provider
+# GitLab as OAuth2 authentication service provider
-This document is about using GitLab as an OAuth authentication service provider to sign into other services.
-If you want to use other OAuth authentication service providers to sign into GitLab please see the [OAuth2 client documentation](../api/oauth2.md)
+This document is about using GitLab as an OAuth authentication service provider
+to sign in to other services.
-OAuth2 provides client applications a 'secure delegated access' to server resources on behalf of a resource owner. Or you can allow users to sign in to your application with their GitLab.com account.
-In fact OAuth allows to issue access token to third-party clients by an authorization server,
-with the approval of the resource owner, or end-user.
-Mostly, OAuth2 is using for SSO (Single sign-on). But you can find a lot of different usages for this functionality.
-For example, our feature 'GitLab Importer' is using OAuth protocol to give an access to repositories without sharing user credentials to GitLab.com account.
-Also GitLab.com application can be used for authentication to your GitLab instance if needed [GitLab OmniAuth](gitlab.md).
+If you want to use other OAuth authentication service providers to sign in to
+GitLab, please see the [OAuth2 client documentation](../api/oauth2.md).
-GitLab has two ways to add new OAuth2 application to an instance, you can add application as regular user and through admin area. So GitLab actually can have an instance-wide and a user-wide applications. There is no defferences between them except the different permission levels.
+## Introduction to OAuth
-### Adding application through profile
-Go to your profile section 'Application' and press button 'New Application'
+[OAuth] provides to client applications a 'secure delegated access' to server
+resources on behalf of a resource owner. In fact, OAuth allows an authorization
+server to issue access tokens to third-party clients with the approval of the
+resource owner, or the end-user.
-![applications](oauth_provider/user_wide_applications.png)
+OAuth is mostly used as a Single Sign-On service (SSO), but you can find a
+lot of different uses for this functionality. For example, you can allow users
+to sign in to your application with their GitLab.com account, or GitLab.com
+can be used for authentication to your GitLab instance
+(see [GitLab OmniAuth](gitlab.md)).
-After this you will see application form, where "Name" is arbitrary name, "Redirect URI" is URL in your app where users will be sent after authorization on GitLab.com.
+The 'GitLab Importer' feature is also using the OAuth protocol to give access
+to repositories without sharing user credentials to your GitLab.com account.
-![application_form](oauth_provider/application_form.png)
+---
-### Authorized application
-Every application you authorized will be shown in your "Authorized application" sections.
+GitLab supports two ways of adding a new OAuth2 application to an instance. You
+can either add an application as a regular user or add it in the admin area.
+What this means is that GitLab can actually have instance-wide and a user-wide
+applications. There is no difference between them except for the different
+permission levels they are set (user/admin).
-![authorized_application](oauth_provider/authorized_application.png)
+## Adding an application through the profile
-At any time you can revoke access just clicking button "Revoke"
+In order to add a new application via your profile, navigate to
+**Profile Settings > Applications** and select **New Application**.
-### OAuth applications in admin area
+![New OAuth application](img/oauth_provider_user_wide_applications.png)
-If you want to create application that does not belong to certain user you can create it from admin area
+---
-![admin_application](oauth_provider/admin_application.png)
\ No newline at end of file
+In the application form, enter a **Name** (arbitrary), and make sure to set up
+correctly the **Redirect URI** which is the URL where users will be sent after
+they authorize with GitLab.
+
+![New OAuth application form](img/oauth_provider_application_form.png)
+
+---
+
+When you hit **Submit** you will be provided with the application ID and
+the application secret which you can then use with your application that
+connects to GitLab.
+
+![OAuth application ID and secret](img/oauth_provider_application_id_secret.png)
+
+---
+
+## OAuth applications in the admin area
+
+To create an application that does not belong to a certain user, you can create
+it from the admin area.
+
+![OAuth admin_applications](img/oauth_provider_admin_application.png)
+
+---
+
+## Authorized applications
+
+Every application you authorized to use your GitLab credentials will be shown
+in the **Authorized applications** section under **Profile Settings > Applications**.
+
+![Authorized_applications](img/oauth_provider_authorized_application.png)
+
+---
+
+As you can see, the default scope `api` is used, which is the only scope that
+GitLab supports so far. At any time you can revoke any access by just clicking
+**Revoke**.
+
+[oauth]: http://oauth.net/2/ "OAuth website"
diff --git a/doc/integration/oauth_provider/admin_application.png b/doc/integration/oauth_provider/admin_application.png
deleted file mode 100644
index a5f34512aa..0000000000
Binary files a/doc/integration/oauth_provider/admin_application.png and /dev/null differ
diff --git a/doc/integration/oauth_provider/application_form.png b/doc/integration/oauth_provider/application_form.png
deleted file mode 100644
index ae135db262..0000000000
Binary files a/doc/integration/oauth_provider/application_form.png and /dev/null differ
diff --git a/doc/integration/oauth_provider/authorized_application.png b/doc/integration/oauth_provider/authorized_application.png
deleted file mode 100644
index d3ce05be9c..0000000000
Binary files a/doc/integration/oauth_provider/authorized_application.png and /dev/null differ
diff --git a/doc/integration/oauth_provider/user_wide_applications.png b/doc/integration/oauth_provider/user_wide_applications.png
deleted file mode 100644
index 719e197406..0000000000
Binary files a/doc/integration/oauth_provider/user_wide_applications.png and /dev/null differ
diff --git a/doc/integration/omniauth.md b/doc/integration/omniauth.md
index e9e17eb416..8e6627b2be 100644
--- a/doc/integration/omniauth.md
+++ b/doc/integration/omniauth.md
@@ -9,6 +9,23 @@ Configuring OmniAuth does not prevent standard GitLab authentication or LDAP (if
- [Enable OmniAuth for an Existing User](#enable-omniauth-for-an-existing-user)
- [OmniAuth configuration sample when using Omnibus GitLab](https://gitlab.com/gitlab-org/omnibus-gitlab/tree/master#omniauth-google-twitter-github-login)
+## Supported Providers
+
+This is a list of the current supported OmniAuth providers. Before proceeding
+on each provider's documentation, make sure to first read this document as it
+contains some settings that are common for all providers.
+
+- [GitHub](github.md)
+- [Bitbucket](bitbucket.md)
+- [GitLab.com](gitlab.md)
+- [Google](google.md)
+- [Facebook](facebook.md)
+- [Twitter](twitter.md)
+- [Shibboleth](shibboleth.md)
+- [SAML](saml.md)
+- [Crowd](crowd.md)
+- [Azure](azure.md)
+
## 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.
@@ -67,19 +84,6 @@ If you want to change these settings:
Now we can choose one or more of the Supported Providers below to continue configuration.
-## Supported Providers
-
-- [GitHub](github.md)
-- [Bitbucket](bitbucket.md)
-- [GitLab.com](gitlab.md)
-- [Google](google.md)
-- [Facebook](facebook.md)
-- [Twitter](twitter.md)
-- [Shibboleth](shibboleth.md)
-- [SAML](saml.md)
-- [Crowd](crowd.md)
-- [Azure](azure.md)
-
## 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.
diff --git a/doc/integration/saml.md b/doc/integration/saml.md
index 1632e42f70..8841dbdb7c 100644
--- a/doc/integration/saml.md
+++ b/doc/integration/saml.md
@@ -78,6 +78,18 @@ On the sign in page there should now be a SAML button below the regular sign in
## Troubleshooting
-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.
+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.
\ No newline at end of file
+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.
+
+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
+message `Can't verify CSRF token authenticity`. This means that there is an error during
+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
diff --git a/doc/integration/slack.md b/doc/integration/slack.md
index 84f1d74c05..ecbe0d3e88 100644
--- a/doc/integration/slack.md
+++ b/doc/integration/slack.md
@@ -6,15 +6,17 @@ To enable Slack integration you must create an Incoming WebHooks integration on
1. [Sign in to Slack](https://slack.com/signin)
-1. Select **Configure Integrations** from the dropdown next to your team name.
+1. Select **Apps & Custom Integrations** from the dropdown next to your team name.
-1. Select the **All Services** tab
+1. Click the **Configure** link (right-upper corner).
-1. Click **Add** next to Incoming Webhooks
+1. Select the **Custom integrations** tab.
-1. Pick Incoming WebHooks
+1. Click the **Incoming WebHooks** row.
-1. Choose the channel name you want to send notifications to
+1. Click the **Add configuration** button.
+
+1. Choose the channel name you want to send notifications to.
1. Click **Add Incoming WebHooks Integration**
- Optional step; You can change bot's name and avatar by clicking modifying the bot name or avatar under **Integration Settings**.
diff --git a/doc/integration/twitter.md b/doc/integration/twitter.md
index 52ed4a2233..4769f26b25 100644
--- a/doc/integration/twitter.md
+++ b/doc/integration/twitter.md
@@ -14,7 +14,7 @@ To enable the Twitter OmniAuth provider you must register your application with
- Callback URL: 'https://gitlab.example.com/users/auth/twitter/callback'
- Agree to the "Developer Agreement".
- ![Twitter App Details](twitter_app_details.png)
+ ![Twitter App Details](img/twitter_app_details.png)
1. Select "Create your Twitter application."
1. Select the "Settings" tab.
@@ -27,7 +27,7 @@ To enable the Twitter OmniAuth provider you must register your application with
1. You should now see an API key and API secret (see screenshot). Keep this page open as you continue configuration.
- ![Twitter app](twitter_app_api_keys.png)
+ ![Twitter app](img/twitter_app_api_keys.png)
1. On your GitLab server, open the configuration file.
@@ -76,4 +76,4 @@ To enable the Twitter OmniAuth provider you must register your application with
1. Restart GitLab for the changes to take effect.
-On the sign in page there should now be a Twitter icon below the regular sign in form. Click the icon to begin the authentication process. Twitter will ask the user to sign in and authorize the GitLab application. If everything goes well the user will be returned to GitLab and will be signed in.
\ No newline at end of file
+On the sign in page there should now be a Twitter icon below the regular sign in form. Click the icon to begin the authentication process. Twitter will ask the user to sign in and authorize the GitLab application. If everything goes well the user will be returned to GitLab and will be signed in.
diff --git a/doc/markdown/img/logo.png b/doc/markdown/img/logo.png
new file mode 100644
index 0000000000..7da5f23ed9
Binary files /dev/null and b/doc/markdown/img/logo.png differ
diff --git a/doc/markdown/markdown.md b/doc/markdown/markdown.md
index bc8e7d155e..c400cdac64 100644
--- a/doc/markdown/markdown.md
+++ b/doc/markdown/markdown.md
@@ -88,6 +88,9 @@ GFM will autolink almost any URL you copy and paste into your text.
## Code and Syntax Highlighting
+_GitLab uses the [rouge ruby library][rouge] for syntax highlighting. For a
+list of supported languages visit the rouge website._
+
Blocks of code are either fenced by lines with three back-ticks ```
, or are indented with four spaces. Only the fenced code blocks support syntax highlighting.
```no-highlight
@@ -421,24 +424,24 @@ will point the link to `wikis/style` when the link is inside of a wiki markdown
Here's our logo (hover to see the title text):
Inline-style:
- ![alt text](assets/logo-white.png)
+ ![alt text](img/logo.png)
Reference-style:
![alt text1][logo]
- [logo]: assets/logo-white.png
+ [logo]: img/logo.png
Here's our logo:
Inline-style:
-![alt text](/assets/logo-white.png)
+![alt text](img/logo.png)
Reference-style:
![alt text][logo]
-[logo]: /assets/logo-white.png
+[logo]: img/logo.png
## Blockquotes
@@ -585,3 +588,5 @@ By including colons in the header row, you can align the text within that column
- This document leveraged heavily from the [Markdown-Cheatsheet](https://github.com/adam-p/markdown-here/wiki/Markdown-Cheatsheet).
- The [Markdown Syntax Guide](https://daringfireball.net/projects/markdown/syntax) at Daring Fireball is an excellent resource for a detailed explanation of standard markdown.
- [Dillinger.io](http://dillinger.io) is a handy tool for testing standard markdown.
+
+[rouge]: http://rouge.jneen.net/ "Rouge website"
diff --git a/doc/permissions/permissions.md b/doc/permissions/permissions.md
index 1be78ac182..168e7d143e 100644
--- a/doc/permissions/permissions.md
+++ b/doc/permissions/permissions.md
@@ -18,6 +18,9 @@ documentation](../workflow/add-user/add-user.md).
|---------------------------------------|---------|------------|-------------|----------|--------|
| Create new issue | ✓ | ✓ | ✓ | ✓ | ✓ |
| Leave comments | ✓ | ✓ | ✓ | ✓ | ✓ |
+| See a list of builds | ✓ [^1] | ✓ | ✓ | ✓ | ✓ |
+| See a build log | ✓ [^1] | ✓ | ✓ | ✓ | ✓ |
+| Download and browse build artifacts | ✓ [^1] | ✓ | ✓ | ✓ | ✓ |
| Pull project code | | ✓ | ✓ | ✓ | ✓ |
| Download project | | ✓ | ✓ | ✓ | ✓ |
| Create code snippets | | ✓ | ✓ | ✓ | ✓ |
@@ -31,6 +34,7 @@ documentation](../workflow/add-user/add-user.md).
| Remove non-protected branches | | | ✓ | ✓ | ✓ |
| Add tags | | | ✓ | ✓ | ✓ |
| Write a wiki | | | ✓ | ✓ | ✓ |
+| Cancel and retry builds | | | ✓ | ✓ | ✓ |
| Create new milestones | | | | ✓ | ✓ |
| Add new team members | | | | ✓ | ✓ |
| Push to protected branches | | | | ✓ | ✓ |
@@ -40,12 +44,17 @@ documentation](../workflow/add-user/add-user.md).
| Edit project | | | | ✓ | ✓ |
| Add deploy keys to project | | | | ✓ | ✓ |
| Configure project hooks | | | | ✓ | ✓ |
+| Manage runners | | | | ✓ | ✓ |
+| Manage build triggers | | | | ✓ | ✓ |
+| Manage variables | | | | ✓ | ✓ |
| Switch visibility level | | | | | ✓ |
| Transfer project to another namespace | | | | | ✓ |
| Remove project | | | | | ✓ |
| Force push to protected branches | | | | | |
| Remove protected branches | | | | | |
+[^1]: If **Allow guest to access builds** is enabled in CI settings
+
## Group
In order for a group to appear as public and be browsable, it must contain at
diff --git a/doc/profile/preferences.md b/doc/profile/preferences.md
index f17bbe8f2a..073b879750 100644
--- a/doc/profile/preferences.md
+++ b/doc/profile/preferences.md
@@ -12,6 +12,9 @@ The default is **Charcoal**.
## Syntax highlighting theme
+_GitLab uses the [rouge ruby library][rouge] for syntax highlighting. For a
+list of supported languages visit the rouge website._
+
Changing this setting allows the user to customize the theme used when viewing
syntax highlighted code on the site.
@@ -36,3 +39,5 @@ The default is **Your Projects**.
It allows user to choose what content he or she want to see on project page.
The default is **Readme**.
+
+[rouge]: http://rouge.jneen.net/ "Rouge website"
diff --git a/doc/project_services/builds_emails.md b/doc/project_services/builds_emails.md
new file mode 100644
index 0000000000..af0b1a287c
--- /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 0000000000..e604dd73ff
Binary files /dev/null and b/doc/project_services/img/builds_emails_service.png differ
diff --git a/doc/project_services/img/jira_add_gitlab_commit_message.png b/doc/project_services/img/jira_add_gitlab_commit_message.png
new file mode 100644
index 0000000000..85e54861b3
Binary files /dev/null and b/doc/project_services/img/jira_add_gitlab_commit_message.png differ
diff --git a/doc/project_services/img/jira_add_user_to_group.png b/doc/project_services/img/jira_add_user_to_group.png
new file mode 100644
index 0000000000..e457643388
Binary files /dev/null and b/doc/project_services/img/jira_add_user_to_group.png differ
diff --git a/doc/project_services/img/jira_create_new_group.png b/doc/project_services/img/jira_create_new_group.png
new file mode 100644
index 0000000000..edaa132605
Binary files /dev/null and b/doc/project_services/img/jira_create_new_group.png differ
diff --git a/doc/project_services/img/jira_create_new_group_name.png b/doc/project_services/img/jira_create_new_group_name.png
new file mode 100644
index 0000000000..9e518ad784
Binary files /dev/null and b/doc/project_services/img/jira_create_new_group_name.png differ
diff --git a/doc/project_services/img/jira_create_new_user.png b/doc/project_services/img/jira_create_new_user.png
new file mode 100644
index 0000000000..57e433dd81
Binary files /dev/null and b/doc/project_services/img/jira_create_new_user.png differ
diff --git a/doc/project_services/img/jira_group_access.png b/doc/project_services/img/jira_group_access.png
new file mode 100644
index 0000000000..47716ca6d0
Binary files /dev/null and b/doc/project_services/img/jira_group_access.png differ
diff --git a/doc/project_services/img/jira_issue_closed.png b/doc/project_services/img/jira_issue_closed.png
new file mode 100644
index 0000000000..cabec1ae13
Binary files /dev/null and b/doc/project_services/img/jira_issue_closed.png differ
diff --git a/doc/integration/img/jira_issue_reference.png b/doc/project_services/img/jira_issue_reference.png
similarity index 100%
rename from doc/integration/img/jira_issue_reference.png
rename to doc/project_services/img/jira_issue_reference.png
diff --git a/doc/project_services/img/jira_issues_workflow.png b/doc/project_services/img/jira_issues_workflow.png
new file mode 100644
index 0000000000..28e17be3a8
Binary files /dev/null and b/doc/project_services/img/jira_issues_workflow.png differ
diff --git a/doc/integration/img/jira_merge_request_close.png b/doc/project_services/img/jira_merge_request_close.png
similarity index 100%
rename from doc/integration/img/jira_merge_request_close.png
rename to doc/project_services/img/jira_merge_request_close.png
diff --git a/doc/integration/img/jira_project_name.png b/doc/project_services/img/jira_project_name.png
similarity index 100%
rename from doc/integration/img/jira_project_name.png
rename to doc/project_services/img/jira_project_name.png
diff --git a/doc/project_services/img/jira_reference_commit_message_in_jira_issue.png b/doc/project_services/img/jira_reference_commit_message_in_jira_issue.png
new file mode 100644
index 0000000000..0149181dc8
Binary files /dev/null and b/doc/project_services/img/jira_reference_commit_message_in_jira_issue.png differ
diff --git a/doc/integration/img/jira_service.png b/doc/project_services/img/jira_service.png
similarity index 100%
rename from doc/integration/img/jira_service.png
rename to doc/project_services/img/jira_service.png
diff --git a/doc/integration/img/jira_service_close_issue.png b/doc/project_services/img/jira_service_close_issue.png
similarity index 100%
rename from doc/integration/img/jira_service_close_issue.png
rename to doc/project_services/img/jira_service_close_issue.png
diff --git a/doc/integration/img/jira_service_page.png b/doc/project_services/img/jira_service_page.png
similarity index 100%
rename from doc/integration/img/jira_service_page.png
rename to doc/project_services/img/jira_service_page.png
diff --git a/doc/project_services/img/jira_submit_gitlab_merge_request.png b/doc/project_services/img/jira_submit_gitlab_merge_request.png
new file mode 100644
index 0000000000..e935d9362a
Binary files /dev/null and b/doc/project_services/img/jira_submit_gitlab_merge_request.png differ
diff --git a/doc/project_services/img/jira_user_management_link.png b/doc/project_services/img/jira_user_management_link.png
new file mode 100644
index 0000000000..2745916972
Binary files /dev/null and b/doc/project_services/img/jira_user_management_link.png differ
diff --git a/doc/integration/img/jira_workflow_screenshot.png b/doc/project_services/img/jira_workflow_screenshot.png
similarity index 100%
rename from doc/integration/img/jira_workflow_screenshot.png
rename to doc/project_services/img/jira_workflow_screenshot.png
diff --git a/doc/project_services/jira.md b/doc/project_services/jira.md
new file mode 100644
index 0000000000..7c12557a32
--- /dev/null
+++ b/doc/project_services/jira.md
@@ -0,0 +1,221 @@
+# GitLab JIRA integration
+
+_**Note:**
+Full JIRA integration was previously exclusive to GitLab Enterprise Edition.
+With [GitLab 8.3 forward][8_3_post], this feature in now [backported][jira-ce]
+to GitLab Community Edition as well._
+
+---
+
+GitLab can be configured to interact with [JIRA Core] either using an
+on-premises instance or the SaaS solution that Atlassian offers. Configuration
+happens via username and password on a per-project basis. Connecting to a JIRA
+server via CAS is not possible.
+
+Each project can be configured to connect to a different JIRA instance or, in
+case you have a single JIRA instance, you can pre-fill the JIRA service
+settings page in GitLab with a default template. To configure the JIRA template,
+see the [Services Templates documentation][services-templates].
+
+Once the GitLab project is connected to JIRA, you can reference and close the
+issues in JIRA directly from GitLab's merge requests.
+
+## Configuration
+
+The configuration consists of two parts:
+
+- [JIRA configuration](#configuring-jira)
+- [GitLab configuration](#configuring-gitlab)
+
+### Configuring JIRA
+
+First things first, we need to create a user in JIRA which will have access to
+all projects that need to integrate with GitLab.
+
+We have split this stage in steps so it is easier to follow.
+
+---
+
+1. Login to your JIRA instance as an administrator and under **Administration**
+ go to **User Management** to create a new user.
+
+ ![JIRA user management link](img/jira_user_management_link.png)
+
+ ---
+
+1. The next step is to create a new user (e.g., `gitlab`) who has write access
+ to projects in JIRA. Enter the user's name and a _valid_ e-mail address
+ since JIRA sends a verification e-mail to set-up the password.
+ _**Note:** JIRA creates the username automatically by using the e-mail
+ prefix. You can change it later if you want._
+
+ ![JIRA create new user](img/jira_create_new_user.png)
+
+ ---
+
+1. Now, let's create a `gitlab-developers` group which will have write access
+ to projects in JIRA. Go to the **Groups** tab and select **Create group**.
+
+ ![JIRA create new user](img/jira_create_new_group.png)
+
+ ---
+
+ Give it an optional description and hit **Create group**.
+
+ ![JIRA create new group](img/jira_create_new_group_name.png)
+
+ ---
+
+1. Give the newly-created group write access by going to
+ **Application access > View configuration** and adding the `gitlab-developers`
+ group to JIRA Core.
+
+ ![JIRA group access](img/jira_group_access.png)
+
+ ---
+
+1. Add the `gitlab` user to the `gitlab-developers` group by going to
+ **Users > GitLab user > Add group** and selecting the `gitlab-developers`
+ group from the dropdown menu. Notice that the group says _Access_ which is
+ what we aim for.
+
+ ![JIRA add user to group](img/jira_add_user_to_group.png)
+
+---
+
+The JIRA configuration is over. Write down the new JIRA username and its
+password as they will be needed when configuring GitLab in the next section.
+
+### Configuring GitLab
+
+_**Note:** The currently supported JIRA versions are v6.x and v7.x. and GitLab
+7.8 or higher is required._
+
+---
+
+Assuming you [have already configured JIRA](#configuring-jira), now it's time
+to configure GitLab.
+
+JIRA configuration in GitLab is done via a project's
+[**Services**](../project_services/project_services.md).
+
+To enable JIRA integration in a project, navigate to the project's
+**Settings > Services > JIRA**.
+
+Fill in the required details on the page, as described in the table below.
+
+| Setting | Description |
+| ------- | ----------- |
+| `Description` | A name for the issue tracker (to differentiate between instances, for example). |
+| `Project url` | The URL to the JIRA project which is being linked to this GitLab project. It is of the form: `https:///issues/?jql=project=`. |
+| `Issues url` | The URL to the JIRA project issues overview for the project that is linked to this GitLab project. It is of the form: `https:///browse/:id`. Leave `:id` as-is, it gets replaced by GitLab at runtime. |
+| `New issue url` | This is the URL to create a new issue in JIRA for the project linked to this GitLab project, and it is of the form: `https:///secure/CreateIssue.jspa` |
+| `Api url` | The base URL of the JIRA API. It may be omitted, in which case GitLab will automatically use API version `2` based on the `project url`. It is of the form: `https:///rest/api/2`. |
+| `Username` | The username of the user created in [configuring JIRA step](#configuring-jira). |
+| `Password` |The password of the user created in [configuring JIRA step](#configuring-jira). |
+| `JIRA issue transition` | This setting is very important to set up correctly. It is the ID of a transition that moves issues to a closed state. You can find this number under the JIRA workflow administration (**Administration > Issues > Workflows**) by selecting **View** under **Operations** of the desired workflow of your project. The ID of each state can be found inside the parenthesis of each transition name under the **Transitions (id)** column ([see screenshot](img/jira_issues_workflow.png)). By default, this ID is set to `2` |
+
+After saving the configuration, your GitLab project will be able to interact
+with the linked JIRA project.
+
+![JIRA service page](img/jira_service_page.png)
+
+---
+
+## JIRA issues
+
+By now you should have [configured JIRA](#configuring-jira) and enabled the
+[JIRA service in GitLab](#configuring-gitlab). If everything is set up correctly
+you should be able to reference and close JIRA issues by just mentioning their
+ID in GitLab commits and merge requests.
+
+### Referencing JIRA Issues
+
+If you reference a JIRA issue, e.g., `GITLAB-1`, in a commit comment, a link
+which points back to JIRA is created.
+
+The same works for comments in merge requests as well.
+
+![JIRA add GitLab commit message](img/jira_add_gitlab_commit_message.png)
+
+---
+
+The mentioning action is two-fold, so a comment with a JIRA issue in GitLab
+will automatically add a comment in that particular JIRA issue with the link
+back to GitLab.
+
+
+![JIRA reference commit message](img/jira_reference_commit_message_in_jira_issue.png)
+
+---
+
+The comment on the JIRA issue is of the form:
+
+> USER mentioned this issue in LINK_TO_THE_MENTION
+
+Where:
+
+| Format | Description |
+| ------ | ----------- |
+| `USER` | A user that mentioned the issue. This is the link to the user profile in GitLab. |
+| `LINK_TO_THE_MENTION` | Link to the origin of mention with a name of the entity where JIRA issue was mentioned. Can be commit or merge request. |
+
+### Closing JIRA issues
+
+JIRA issues can be closed directly from GitLab by using trigger words in
+commits and merge requests. When a commit which contains the trigger word
+followed by the JIRA issue ID in the commit message is pushed, GitLab will
+add a comment in the mentioned JIRA issue and immediately close it (provided
+the transition ID was set up correctly).
+
+There are currently three trigger words, and you can use either one to achieve
+the same goal:
+
+- `Resolves GITLAB-1`
+- `Closes GITLAB-1`
+- `Fixes GITLAB-1`
+
+where `GITLAB-1` the issue ID of the JIRA project.
+
+### JIRA issue closing example
+
+Let's say for example that we submitted a bug fix and created a merge request
+in GitLab. The workflow would be something like this:
+
+1. Create a new branch
+1. Fix the bug
+1. Commit the changes and push branch to GitLab
+1. Open a new merge request and reference the JIRA issue including one of the
+ trigger words, e.g.: `Fixes GITLAB-1`, in the description
+1. Submit the merge request
+1. Ask someone to review
+1. Merge the merge request
+1. The JIRA issue is automatically closed
+
+---
+
+In the following screenshot you can see what the link references to the JIRA
+issue look like.
+
+![JIRA - submit a GitLab merge request](img/jira_submit_gitlab_merge_request.png)
+
+---
+
+Once this merge request is merged, the JIRA issue will be automatically closed
+with a link to the commit that resolved the issue.
+
+![The GitLab integration user leaves a comment on JIRA](img/jira_issue_closed.png)
+
+---
+
+You can see from the above image that there are four references to GitLab:
+
+- The first is from a comment in a specific commit
+- The second is from the JIRA issue reference in the merge request description
+- The third is from the actual commit that solved the issue
+- And the fourth is from the commit that the merge request created
+
+[services-templates]: ../project_services/services_templates.md "Services templates documentation"
+[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"
diff --git a/doc/project_services/project_services.md b/doc/project_services/project_services.md
index e340312772..3fea2cff0b 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 |
@@ -22,7 +22,7 @@ further configuration instructions and details. Contributions are welcome.
| Gemnasium | Gemnasium monitors your project dependencies and alerts you about updates and security vulnerabilities |
| [HipChat](hipchat.md) | Private group chat and IM |
| [Irker (IRC gateway)](irker.md) | Send IRC messages, on update, to a list of recipients through an Irker gateway |
-| JIRA | Jira issue tracker |
+| [JIRA](jira.md) | JIRA issue tracker |
| JetBrains TeamCity CI | A continuous integration and build server |
| PivotalTracker | Project Management Software (Source Commits Endpoint) |
| Pushover | Pushover makes it easy to get real-time notifications on your Android device, iPhone, iPad, and Desktop |
diff --git a/doc/raketasks/backup_restore.md b/doc/raketasks/backup_restore.md
index cdd6652b7b..f6d1234ac4 100644
--- a/doc/raketasks/backup_restore.md
+++ b/doc/raketasks/backup_restore.md
@@ -18,8 +18,6 @@ for two-factor authentication. If you restore a GitLab backup without
restoring the database encryption key, users who have two-factor
authentication enabled will lose access to your GitLab server.
-If you are interested in GitLab CI backup please follow to the [CI backup documentation](https://gitlab.com/gitlab-org/gitlab-ci/blob/master/doc/raketasks/backup_restore.md)*
-
```
# use this command if you've installed GitLab with the Omnibus package
sudo gitlab-rake gitlab:backup:create
diff --git a/doc/security/README.md b/doc/security/README.md
index f34c792d00..be1abb88c3 100644
--- a/doc/security/README.md
+++ b/doc/security/README.md
@@ -7,4 +7,4 @@
- [Reset your root password](reset_root_password.md)
- [User File Uploads](user_file_uploads.md)
- [How we manage the CRIME vulnerability](crime_vulnerability.md)
-- [Enforce Two-Factor authentication](two_factor_authentication.md)
+- [Enforce Two-factor authentication](two_factor_authentication.md)
diff --git a/doc/security/img/two_factor_authentication_settings.png b/doc/security/img/two_factor_authentication_settings.png
new file mode 100644
index 0000000000..aa51ce030b
Binary files /dev/null and b/doc/security/img/two_factor_authentication_settings.png differ
diff --git a/doc/security/two_factor_authentication.md b/doc/security/two_factor_authentication.md
index 4e25a1fdc3..8365bdb7b1 100644
--- a/doc/security/two_factor_authentication.md
+++ b/doc/security/two_factor_authentication.md
@@ -20,7 +20,13 @@ In the Admin area under **Settings** (`/admin/application_settings`), look for
the "Sign-in Restrictions" area, where you can configure both.
If you want 2FA enforcement to take effect on next login, change the grace
-period to `0`
+period to `0`.
+
+---
+
+![Two factor authentication admin settings](img/two_factor_authentication_settings.png)
+
+---
## Disabling 2FA for everyone
@@ -28,11 +34,12 @@ There may be some special situations where you want to disable 2FA for everyone
even when forced 2FA is disabled. There is a rake task for that:
```
-# use this command if you've installed GitLab with the Omnibus package
+# Omnibus installations
sudo gitlab-rake gitlab:two_factor:disable_for_all_users
-# if you've installed GitLab from source
+# Installations from source
sudo -u git -H bundle exec rake gitlab:two_factor:disable_for_all_users RAILS_ENV=production
```
-**IMPORTANT: this is a permanent and irreversible action. Users will have to reactivate 2FA from scratch if they want to use it again.**
+**IMPORTANT: this is a permanent and irreversible action. Users will have to
+ reactivate 2FA from scratch if they want to use it again.**
diff --git a/doc/ssh/README.md b/doc/ssh/README.md
index 77eb53427e..a1198e5878 100644
--- a/doc/ssh/README.md
+++ b/doc/ssh/README.md
@@ -5,6 +5,12 @@
An SSH key allows you to establish a secure connection between your
computer and GitLab. Before generating an SSH key in your shell, check if your system
already has one by running the following command:
+
+**Windows Command Line:**
+```bash
+type %userprofile%\.ssh\id_rsa.pub
+```
+**GNU/Linux/Mac/PowerShell:**
```bash
cat ~/.ssh/id_rsa.pub
```
@@ -25,6 +31,12 @@ press enter to use the default. If you use a different name, the key will not
be used automatically.
Use the command below to show your public key:
+
+**Windows Command Line:**
+```bash
+type %userprofile%\.ssh\id_rsa.pub
+```
+**GNU/Linux/Mac/PowerShell:**
```bash
cat ~/.ssh/id_rsa.pub
```
@@ -36,9 +48,14 @@ with your username and host.
To copy your public key to the clipboard, use the code below. Depending on your
OS you'll need to use a different command:
-**Windows:**
+**Windows Command Line:**
```bash
-clip < ~/.ssh/id_rsa.pub
+type %userprofile%\.ssh\id_rsa.pub | clip
+```
+
+**Windows PowerShell:**
+```bash
+cat ~/.ssh/id_rsa.pub | clip
```
**Mac:**
diff --git a/doc/update/6.x-or-7.x-to-7.14.md b/doc/update/6.x-or-7.x-to-7.14.md
index 4516a10208..c45fc9340e 100644
--- a/doc/update/6.x-or-7.x-to-7.14.md
+++ b/doc/update/6.x-or-7.x-to-7.14.md
@@ -14,6 +14,12 @@ possible to edit the label text and color. The characters `?`, `&` and `,` are
no longer allowed however so those will be removed from your tags during the
database migrations for GitLab 7.2.
+## Stash changes
+
+If you [deleted the vendors folder during your original installation](https://github.com/gitlabhq/gitlabhq/issues/4883#issuecomment-31108431), [you will get an error](https://gitlab.com/gitlab-org/gitlab-ce/issues/1494) when you attempt to rebuild the assets in step 7. To avoid this, stash the changes in your GitLab working copy before starting:
+
+ git stash
+
## 0. Stop server
sudo service gitlab stop
diff --git a/doc/update/8.4-to-8.5.md b/doc/update/8.4-to-8.5.md
new file mode 100644
index 0000000000..408a17ac34
--- /dev/null
+++ b/doc/update/8.4-to-8.5.md
@@ -0,0 +1,142 @@
+# From 8.4 to 8.5
+
+### 1. Stop server
+
+ sudo service gitlab stop
+
+### 2. Backup
+
+```bash
+cd /home/git/gitlab
+sudo -u git -H bundle exec rake gitlab:backup:create RAILS_ENV=production
+```
+
+### 3. Get latest code
+
+```bash
+sudo -u git -H git fetch --all
+sudo -u git -H git checkout -- db/schema.rb # local changes will be restored automatically
+```
+
+For GitLab Community Edition:
+
+```bash
+sudo -u git -H git checkout 8-5-stable
+```
+
+OR
+
+For GitLab Enterprise Edition:
+
+```bash
+sudo -u git -H git checkout 8-5-stable-ee
+```
+
+### 4. Update gitlab-shell
+
+```bash
+cd /home/git/gitlab-shell
+sudo -u git -H git fetch --all
+sudo -u git -H git checkout v2.6.10
+```
+
+### 5. Update gitlab-workhorse
+
+Install and compile gitlab-workhorse. This requires
+[Go 1.5](https://golang.org/dl) which should already be on your system from
+GitLab 8.1.
+
+```bash
+cd /home/git/gitlab-workhorse
+sudo -u git -H git fetch --all
+sudo -u git -H git checkout 0.6.4
+sudo -u git -H make
+```
+
+### 6. Install libs, migrations, etc.
+
+```bash
+cd /home/git/gitlab
+
+# MySQL installations (note: the line below states '--without postgres')
+sudo -u git -H bundle install --without postgres development test --deployment
+
+# PostgreSQL installations (note: the line below states '--without mysql')
+sudo -u git -H bundle install --without mysql development test --deployment
+
+# Run database migrations
+sudo -u git -H bundle exec rake db:migrate RAILS_ENV=production
+
+# Clean up assets and cache
+sudo -u git -H bundle exec rake assets:clean assets:precompile cache:clear RAILS_ENV=production
+
+```
+
+### 7. Update configuration files
+
+#### New configuration options for `gitlab.yml`
+
+There are new configuration options available for [`gitlab.yml`](config/gitlab.yml.example). View them with the command below and apply them manually to your current `gitlab.yml`:
+
+```sh
+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
+ sudo service nginx restart
+
+### 9. Check application status
+
+Check if GitLab and its environment are configured correctly:
+
+ sudo -u git -H bundle exec rake gitlab:env:info RAILS_ENV=production
+
+To make sure you didn't miss anything run a more thorough check:
+
+ sudo -u git -H bundle exec rake gitlab:check RAILS_ENV=production
+
+If all items are green, then congratulations, the upgrade is complete!
+
+## Things went south? Revert to previous version (8.4)
+
+### 1. Revert the code to the previous version
+
+Follow the [upgrade guide from 8.3 to 8.4](8.3-to-8.4.md), except for the
+database migration (the backup is already migrated to the previous version).
+
+### 2. Restore from the backup
+
+```bash
+cd /home/git/gitlab
+sudo -u git -H bundle exec rake gitlab:backup:restore RAILS_ENV=production
+```
+
+If you have more than one backup `*.tar` file(s) please add `BACKUP=timestamp_of_backup` to the command above.
diff --git a/doc/web_hooks/web_hooks.md b/doc/web_hooks/web_hooks.md
index 6420d65cf1..b82306bd1d 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.
@@ -8,8 +15,8 @@ Web hooks can be used to update an external issue tracker, trigger CI builds, up
## SSL Verification
-By default, the SSL certificate of the webhook endpoint is verified based on
-an internal list of Certificate Authorities,
+By default, the SSL certificate of the webhook endpoint is verified based on
+an internal list of Certificate Authorities,
which means the certificate cannot be self-signed.
You can turn this off in the web hook settings in your GitLab projects.
@@ -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": "",
@@ -56,7 +80,7 @@ X-Gitlab-Event: Push Hook
"author": {
"name": "Jordi Mallach",
"email": "jordi@softcatala.org"
- }
+ },
"added": ["CHANGELOG"],
"modified": ["app/controller/application.rb"],
"removed": []
@@ -76,7 +100,6 @@ X-Gitlab-Event: Push Hook
}
],
"total_commits_count": 4
-
}
```
@@ -101,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": "",
@@ -136,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.",
@@ -158,6 +214,11 @@ X-Gitlab-Event: Issue Hook
"iid": 23,
"url": "http://example.com/diaspora/issues/23",
"action": "open"
+ },
+ "assignee": {
+ "name": "User1",
+ "username": "user1",
+ "avatar_url": "http://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=40\u0026d=identicon"
}
}
```
@@ -193,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"
},
@@ -256,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"
},
@@ -296,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",
@@ -322,7 +431,12 @@ X-Gitlab-Event: Note Hook
"email": "john@example.com"
}
},
- "work_in_progress": false
+ "work_in_progress": false,
+ "assignee": {
+ "name": "User1",
+ "username": "user1",
+ "avatar_url": "http://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=40\u0026d=identicon"
+ }
}
}
```
@@ -346,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,
@@ -388,7 +518,6 @@ X-Gitlab-Event: Note Hook
### Comment on code snippet
-
**Request header**:
```
@@ -397,7 +526,7 @@ X-Gitlab-Event: Note Hook
**Request body:**
-```
+```json
{
"object_kind": "note",
"user": {
@@ -406,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,
@@ -482,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",
@@ -510,7 +671,12 @@ X-Gitlab-Event: Merge Request Hook
},
"work_in_progress": false,
"url": "http://example.com/diaspora/merge_requests/1",
- "action": "open"
+ "action": "open",
+ "assignee": {
+ "name": "User1",
+ "username": "user1",
+ "avatar_url": "http://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=40\u0026d=identicon"
+ }
}
}
```
diff --git a/doc/workflow/README.md b/doc/workflow/README.md
index bf62ab4105..2ac32373ce 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/forking/fork_button.png b/doc/workflow/forking/fork_button.png
deleted file mode 100644
index def4266476..0000000000
Binary files a/doc/workflow/forking/fork_button.png and /dev/null differ
diff --git a/doc/workflow/forking/groups.png b/doc/workflow/forking/groups.png
deleted file mode 100644
index 3ac64b3c8e..0000000000
Binary files a/doc/workflow/forking/groups.png and /dev/null differ
diff --git a/doc/workflow/forking_workflow.md b/doc/workflow/forking_workflow.md
index 8edf7c6ab3..217a4a4012 100644
--- a/doc/workflow/forking_workflow.md
+++ b/doc/workflow/forking_workflow.md
@@ -1,36 +1,59 @@
# Project forking workflow
-Forking a project to your own namespace is useful if you have no write access to the project you want to contribute
-to. If you do have write access or can request it we recommend working together in the same repository since it is simpler.
-See our **[GitLab Flow](https://about.gitlab.com/2014/09/29/gitlab-flow/)** article for more information about using
-branches to work together.
+Forking a project to your own namespace is useful if you have no write
+access to the project you want to contribute to. If you do have write
+access or can request it, we recommend working together in the same
+repository since it is simpler. See our [GitLab Flow](gitlab_flow.md)
+document more information about using branches to work together.
## Creating a fork
-In order to create a fork of a project, all you need to do is click on the fork button located on the top right side
-of the screen, close to the project's URL and right next to the stars button.
+Forking a project is in most cases a two-step process.
-![Fork button](forking/fork_button.png)
-Once you do that you'll be presented with a screen where you can choose the namespace to fork to. Only namespaces
-(groups and your own namespace) where you have write access to, will be shown. Click on the namespace to create your
-fork there.
+1. Click on the fork button located in the middle of the page or a project's
+ home page right next to the stars button.
-![Groups view](forking/groups.png)
+ ![Fork button](img/forking_workflow_fork_button.png)
-After the forking is done, you can start working on the newly created repository. There you will have full
-[Owner](../permissions/permissions.md) access, so you can set it up as you please.
+ ---
+
+1. Once you do that, you'll be presented with a screen where you can choose
+ the namespace to fork to. Only namespaces (groups and your own
+ namespace) where you have write access to, will be shown. Click on the
+ namespace to create your fork there.
+
+ ![Choose namespace](img/forking_workflow_choose_namespace.png)
+
+ ---
+
+ **Note:**
+ If the namespace you chose to fork the project to has another project with
+ the same path name, you will be presented with a warning that the forking
+ could not be completed. Try to resolve the error and repeat the forking
+ process.
+
+ ![Path taken error](img/forking_workflow_path_taken_error.png)
+
+ ---
+
+After the forking is done, you can start working on the newly created
+repository. There, you will have full [Owner](../permissions/permissions.md)
+access, so you can set it up as you please.
## Merging upstream
-Once you are ready to send your code back to the main project, you need to create a merge request. Choose your forked
-project's main branch as the source and the original project's main branch as the destination and create the merge request.
+Once you are ready to send your code back to the main project, you need
+to create a merge request. Choose your forked project's main branch as
+the source and the original project's main branch as the destination and
+create the [merge request](merge_requests.md).
![Selecting branches](forking/branch_select.png)
-You can then assign the merge request to someone to have them review your changes. Upon pressing the 'Accept Merge Request'
-button, your changes will be added to the repository and branch you're merging into.
+You can then assign the merge request to someone to have them review
+your changes. Upon pressing the 'Accept Merge Request' button, your
+changes will be added to the repository and branch you're merging into.
![New merge request](forking/merge_request.png)
-
+[gitlab flow]: https://about.gitlab.com/2014/09/29/gitlab-flow/ "GitLab Flow blog post"
diff --git a/doc/workflow/gitlab_flow.md b/doc/workflow/gitlab_flow.md
index 8965e5b365..0b205ea6de 100644
--- a/doc/workflow/gitlab_flow.md
+++ b/doc/workflow/gitlab_flow.md
@@ -152,9 +152,10 @@ The name of this branch should start with the issue number, for example '15-requ
When you are done or want to discuss the code you open a merge request.
This is an online place to discuss the change and review the code.
-Creating a branch is a manual action since you do not always want to merge a new branch you push, it could be a long-running environment or release branch.
-If you create the merge request but do not assign it to anyone it is a 'work-in-process' merge request.
+Opening a merge request is a manual action since you do not always want to merge a new branch you push, it could be a long-running environment or release branch.
+If you open the merge request but do not assign it to anyone it is a 'Work In Progress' merge request.
These are used to discuss the proposed implementation but are not ready for inclusion in the master branch yet.
+_Pro tip:_ Start the title of the merge request with `[WIP]` or `WIP:` to prevent it from being merged before it's ready.
When the author thinks the code is ready the merge request is assigned to reviewer.
The reviewer presses the merge button when they think the code is ready for inclusion in the master branch.
@@ -185,13 +186,16 @@ 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.
+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.
@@ -220,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/forking_workflow_choose_namespace.png b/doc/workflow/img/forking_workflow_choose_namespace.png
new file mode 100644
index 0000000000..eefe576955
Binary files /dev/null and b/doc/workflow/img/forking_workflow_choose_namespace.png differ
diff --git a/doc/workflow/img/forking_workflow_fork_button.png b/doc/workflow/img/forking_workflow_fork_button.png
new file mode 100644
index 0000000000..49e68d33e8
Binary files /dev/null and b/doc/workflow/img/forking_workflow_fork_button.png differ
diff --git a/doc/workflow/img/forking_workflow_path_taken_error.png b/doc/workflow/img/forking_workflow_path_taken_error.png
new file mode 100644
index 0000000000..7a3139506f
Binary files /dev/null and b/doc/workflow/img/forking_workflow_path_taken_error.png differ
diff --git a/doc/workflow/img/revert_changes_commit.png b/doc/workflow/img/revert_changes_commit.png
new file mode 100644
index 0000000000..d84211e20d
Binary files /dev/null and b/doc/workflow/img/revert_changes_commit.png 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 0000000000..e94d151a2a
Binary files /dev/null and b/doc/workflow/img/revert_changes_commit_modal.png differ
diff --git a/doc/workflow/img/revert_changes_mr.png b/doc/workflow/img/revert_changes_mr.png
new file mode 100644
index 0000000000..7adad88463
Binary files /dev/null and b/doc/workflow/img/revert_changes_mr.png 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 0000000000..9da78f8482
Binary files /dev/null and b/doc/workflow/img/revert_changes_mr_modal.png differ
diff --git a/doc/workflow/img/todos_icon.png b/doc/workflow/img/todos_icon.png
new file mode 100644
index 0000000000..879b3b51c2
Binary files /dev/null and b/doc/workflow/img/todos_icon.png differ
diff --git a/doc/workflow/img/todos_index.png b/doc/workflow/img/todos_index.png
new file mode 100644
index 0000000000..4ee18dd128
Binary files /dev/null and b/doc/workflow/img/todos_index.png 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 0000000000..009e4b05ad
Binary files /dev/null and b/doc/workflow/img/web_editor_new_branch_dropdown.png 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 0000000000..dd6cfc6e7b
Binary files /dev/null and b/doc/workflow/img/web_editor_new_branch_page.png 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 0000000000..2c76f84f39
Binary files /dev/null and b/doc/workflow/img/web_editor_new_directory_dialog.png 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 0000000000..cedf46aedf
Binary files /dev/null and b/doc/workflow/img/web_editor_new_directory_dropdown.png 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 0000000000..6e884f6504
Binary files /dev/null and b/doc/workflow/img/web_editor_new_file_dropdown.png 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 0000000000..c76473bcfa
Binary files /dev/null and b/doc/workflow/img/web_editor_new_file_editor.png 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 0000000000..a210873574
Binary files /dev/null and b/doc/workflow/img/web_editor_new_push_widget.png 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 0000000000..263dd635b9
Binary files /dev/null and b/doc/workflow/img/web_editor_new_tag_dropdown.png 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 0000000000..64d7cd11ed
Binary files /dev/null and b/doc/workflow/img/web_editor_new_tag_page.png 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 0000000000..be12a151ca
Binary files /dev/null and b/doc/workflow/img/web_editor_start_new_merge_request.png 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 0000000000..6dd2207bca
Binary files /dev/null and b/doc/workflow/img/web_editor_upload_file_dialog.png 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 0000000000..bf6528701b
Binary files /dev/null and b/doc/workflow/img/web_editor_upload_file_dropdown.png differ
diff --git a/doc/workflow/importing/import_projects_from_github.md b/doc/workflow/importing/import_projects_from_github.md
index 77fb7ea7cd..f693f430a4 100644
--- a/doc/workflow/importing/import_projects_from_github.md
+++ b/doc/workflow/importing/import_projects_from_github.md
@@ -5,11 +5,15 @@ enable the [GitHub integration][gh-import] in your GitLab instance._
At its current state, GitHub importer can import:
-- the repository description
-- the git repository data
-- the issues
-- the pull requests
-- the wiki pages
+- the repository description (introduced in GitLab 7.7)
+- the git repository data (introduced in GitLab 7.7)
+- the issues (introduced in GitLab 7.7)
+- the pull requests (introduced in GitLab 8.4)
+- the wiki pages (introduced in GitLab 8.4)
+
+It is not yet possible to import your labels, milestones and cross-repository
+pull requests (those from forks). We are working on improving this in the near
+future.
The importer page is visible when you [create a new project][new-project].
Click on the **GitHub** link and you will be redirected to GitHub for
@@ -35,12 +39,6 @@ The importer will create any new namespaces if they don't exist or in the
case the namespace is taken, the project will be imported on the user's
namespace.
-### Note
-
-When you import your projects from GitHub, it is not possible to keep your
-labels, milestones, and cross-repository pull requests. We are working on
-improving this in the near future.
-
[gh-import]: ../../integration/github.md "GitHub integration"
[ee-gh]: http://doc.gitlab.com/ee/integration/github.html "GitHub integration for GitLab EE"
[new-project]: ../../gitlab-basics/create-project.md "How to create a new project in GitLab"
diff --git a/doc/workflow/protected_branches.md b/doc/workflow/protected_branches.md
index 0adf9f8e3e..fdf9a8d391 100644
--- a/doc/workflow/protected_branches.md
+++ b/doc/workflow/protected_branches.md
@@ -1,6 +1,6 @@
# Protected branches
-Permission in GitLab are fundamentally defined around the idea of having read or write permission to the repository and branches.
+Permissions in GitLab are fundamentally defined around the idea of having read or write permission to the repository and branches.
To prevent people from messing with history or pushing code without review, we've created protected branches.
diff --git a/doc/workflow/revert_changes.md b/doc/workflow/revert_changes.md
new file mode 100644
index 0000000000..399366b0cd
--- /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/todos.md b/doc/workflow/todos.md
new file mode 100644
index 0000000000..5f440fdafd
--- /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 7fc8f96b9e..4a451d9895 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 f480c69ac3..0000000000
Binary files a/doc/workflow/web_editor/edit_file.png and /dev/null differ
diff --git a/doc/workflow/web_editor/empty_project.png b/doc/workflow/web_editor/empty_project.png
deleted file mode 100644
index 6a049f6bea..0000000000
Binary files a/doc/workflow/web_editor/empty_project.png and /dev/null differ
diff --git a/doc/workflow/web_editor/new_file.png b/doc/workflow/web_editor/new_file.png
deleted file mode 100644
index 55ebd9e025..0000000000
Binary files a/doc/workflow/web_editor/new_file.png and /dev/null differ
diff --git a/doc/workflow/web_editor/show_file.png b/doc/workflow/web_editor/show_file.png
deleted file mode 100644
index 9cafcb5510..0000000000
Binary files a/doc/workflow/web_editor/show_file.png and /dev/null differ
diff --git a/features/admin/appearance.feature b/features/admin/appearance.feature
new file mode 100644
index 0000000000..5c1dd7531c
--- /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/admin/broadcast_messages.feature b/features/admin/broadcast_messages.feature
index fd3bac77f8..4f9c651561 100644
--- a/features/admin/broadcast_messages.feature
+++ b/features/admin/broadcast_messages.feature
@@ -25,3 +25,9 @@ Feature: Admin Broadcast Messages
When I remove an existing broadcast message
Then I should be redirected to admin messages page
And I should not see the removed broadcast message
+
+ @javascript
+ Scenario: Live preview a customized broadcast message
+ When I visit admin messages page
+ And I enter a broadcast message with Markdown
+ Then I should see a live preview of the rendered broadcast message
diff --git a/features/admin/spam_logs.feature b/features/admin/spam_logs.feature
new file mode 100644
index 0000000000..92a5389e3a
--- /dev/null
+++ b/features/admin/spam_logs.feature
@@ -0,0 +1,8 @@
+Feature: Admin spam logs
+ Background:
+ Given I sign in as an admin
+ And spam logs exist
+
+ Scenario: Browse spam logs
+ When I visit spam logs page
+ Then I should see list of spam logs
diff --git a/features/dashboard/dashboard.feature b/features/dashboard/dashboard.feature
index b667b587c5..c3b3577c44 100644
--- a/features/dashboard/dashboard.feature
+++ b/features/dashboard/dashboard.feature
@@ -41,3 +41,33 @@ Feature: Dashboard
And user with name "John Doe" left project "Shop"
When I visit dashboard activity page
Then I should see "John Doe left project Shop" event
+
+ @javascript
+ Scenario: Sorting Issues
+ Given I visit dashboard issues page
+ And I sort the list by "Oldest updated"
+ And I visit dashboard activity page
+ And I visit dashboard issues page
+ Then The list should be sorted by "Oldest updated"
+
+ @javascript
+ Scenario: Visiting Project's issues after sorting
+ Given I visit dashboard issues page
+ And I sort the list by "Oldest updated"
+ And I visit project "Shop" issues page
+ Then The list should be sorted by "Oldest updated"
+
+ @javascript
+ Scenario: Sorting Merge Requests
+ Given I visit dashboard merge requests page
+ And I sort the list by "Oldest updated"
+ And I visit dashboard activity page
+ And I visit dashboard merge requests page
+ Then The list should be sorted by "Oldest updated"
+
+ @javascript
+ Scenario: Visiting Project's merge requests after sorting
+ Given I visit dashboard merge requests page
+ And I sort the list by "Oldest updated"
+ And I visit project "Shop" merge requests page
+ Then The list should be sorted by "Oldest updated"
diff --git a/features/dashboard/todos.feature b/features/dashboard/todos.feature
new file mode 100644
index 0000000000..1e7b1b50d6
--- /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/groups.feature b/features/groups.feature
index c803e95298..55fffb012a 100644
--- a/features/groups.feature
+++ b/features/groups.feature
@@ -3,6 +3,10 @@ Feature: Groups
Given I sign in as "John Doe"
And "John Doe" is owner of group "Owned"
+ Scenario: I should not see a group if it does not exist
+ When I visit group "NonExistentGroup" page
+ Then page status code should be 404
+
Scenario: I should have back to group button
When I visit group "Owned" page
Then I should see back to dashboard button
diff --git a/features/login_form.feature b/features/login_form.feature
deleted file mode 100644
index b4d9575448..0000000000
--- 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/project/badges/build.feature b/features/project/badges/build.feature
new file mode 100644
index 0000000000..bcf80ed620
--- /dev/null
+++ b/features/project/badges/build.feature
@@ -0,0 +1,27 @@
+Feature: Project Badges Build
+ Background:
+ Given I sign in as a user
+ And I own a project
+ And project has CI enabled
+ And project has a recent build
+
+ Scenario: I want to see a badge for successfully built project
+ Given recent build is successful
+ When I display builds badge for a master branch
+ Then I should see a build success badge
+
+ Scenario: I want to see a badge for project with failed builds
+ Given recent build failed
+ When I display builds badge for a master branch
+ Then I should see a build failed badge
+
+ Scenario: I want to see a badge for project with running builds
+ Given recent build is successful
+ 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/artifacts.feature b/features/project/builds/artifacts.feature
index 4f68e44fd7..52dc15f2eb 100644
--- a/features/project/builds/artifacts.feature
+++ b/features/project/builds/artifacts.feature
@@ -7,21 +7,21 @@ Feature: Project Builds Artifacts
Scenario: I download build artifacts
Given recent build has artifacts available
- When I visit recent build summary page
+ When I visit recent build details page
And I click artifacts download button
Then download of build artifacts archive starts
Scenario: I browse build artifacts
Given recent build has artifacts available
And recent build has artifacts metadata available
- When I visit recent build summary page
+ When I visit recent build details page
And I click artifacts browse button
Then I should see content of artifacts archive
Scenario: I browse subdirectory of build artifacts
Given recent build has artifacts available
And recent build has artifacts metadata available
- When I visit recent build summary page
+ When I visit recent build details page
And I click artifacts browse button
And I click link to subdirectory within build artifacts
Then I should see content of subdirectory within artifacts archive
@@ -30,7 +30,7 @@ Feature: Project Builds Artifacts
Given recent build has artifacts available
And recent build has artifacts metadata available
And recent build artifacts contain directory with UTF-8 characters
- When I visit recent build summary page
+ When I visit recent build details page
And I click artifacts browse button
And I navigate to directory with UTF-8 characters in name
Then I should see content of directory with UTF-8 characters in name
@@ -39,7 +39,7 @@ Feature: Project Builds Artifacts
Given recent build has artifacts available
And recent build has artifacts metadata available
And recent build artifacts contain directory with invalid UTF-8 characters
- When I visit recent build summary page
+ When I visit recent build details page
And I click artifacts browse button
And I navigate to parent directory of directory with invalid name
Then I should not see directory with invalid name on the list
@@ -47,7 +47,16 @@ Feature: Project Builds Artifacts
Scenario: I download a single file from build artifacts
Given recent build has artifacts available
And recent build has artifacts metadata available
- When I visit recent build summary page
+ When I visit recent build details page
And I click artifacts browse button
And I click a link to file within build artifacts
Then download of a file extracted from build artifacts should start
+
+ @javascript
+ Scenario: I click on a row in an artifacts table
+ Given recent build has artifacts available
+ And recent build has artifacts metadata available
+ When I visit recent build details page
+ And I click artifacts browse button
+ And I click a first row within build artifacts table
+ Then page with a coresponding path is loading
diff --git a/features/project/builds/permissions.feature b/features/project/builds/permissions.feature
index 1193bcd74f..3c7f72335d 100644
--- a/features/project/builds/permissions.feature
+++ b/features/project/builds/permissions.feature
@@ -5,6 +5,41 @@ Feature: Project Builds Permissions
And project has CI enabled
And project has a recent build
+ Scenario: I try to visit build details as guest
+ Given I am member of a project with a guest role
+ When I visit recent build details page
+ Then page status code should be 404
+
+ Scenario: I try to visit project builds page as guest
+ Given I am member of a project with a guest role
+ When I visit project builds page
+ Then page status code should be 404
+
+ Scenario: I try to visit build details of internal project without access to builds
+ Given The project is internal
+ And public access for builds is disabled
+ When I visit recent build details page
+ Then page status code should be 404
+
+ Scenario: I try to visit internal project builds page without access to builds
+ Given The project is internal
+ And public access for builds is disabled
+ When I visit project builds page
+ Then page status code should be 404
+
+ Scenario: I try to visit build details of internal project with access to builds
+ Given The project is internal
+ And public access for builds is enabled
+ When I visit recent build details page
+ Then I see details of a build
+ And I see build trace
+
+ Scenario: I try to visit internal project builds page with access to builds
+ Given The project is internal
+ And public access for builds is enabled
+ When I visit project builds page
+ Then I see the build
+
Scenario: I try to download build artifacts as guest
Given I am member of a project with a guest role
And recent build has artifacts available
diff --git a/features/project/builds/summary.feature b/features/project/builds/summary.feature
index e90ea592aa..4f3fd194d0 100644
--- a/features/project/builds/summary.feature
+++ b/features/project/builds/summary.feature
@@ -5,7 +5,20 @@ Feature: Project Builds Summary
And project has CI enabled
And project has a recent build
- Scenario: I browse build summary page
- When I visit recent build summary page
- Then I see summary for build
+ Scenario: I browse build details page
+ When I visit recent build details page
+ Then I see details of a build
And I see build trace
+
+ Scenario: I browse project builds page
+ When I visit project builds page
+ 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/commits.feature b/features/project/commits/commits.feature
index 01c1072131..a95df03835 100644
--- a/features/project/commits/commits.feature
+++ b/features/project/commits/commits.feature
@@ -7,6 +7,26 @@ Feature: Project Commits
Scenario: I browse commits list for master branch
Then I see project commits
+ And I should not see button to create a new merge request
+ Then I click the "Compare" tab
+ And I should not see button to create a new merge request
+
+ Scenario: I browse commits list for feature branch without a merge request
+ Given I visit commits list page for feature branch
+ Then I see feature branch commits
+ And I see button to create a new merge request
+ Then I click the "Compare" tab
+ And I see button to create a new merge request
+
+ Scenario: I browse commits list for feature branch with an open merge request
+ Given project have an open merge request
+ And I visit commits list page for feature branch
+ Then I see feature branch commits
+ And I should not see button to create a new merge request
+ And I should see button to the merge request
+ Then I click the "Compare" tab
+ And I should not see button to create a new merge request
+ And I should see button to the merge request
Scenario: I browse atom feed of commits list for master branch
Given I click atom feed link
@@ -30,6 +50,22 @@ Feature: Project Commits
And I click side-by-side diff button
Then I see inline diff button
+ @javascript
+ Scenario: I compare branches without a merge request
+ Given I visit compare refs page
+ And I fill compare fields with branches
+ Then I see compared branches
+ And I see button to create a new merge request
+
+ @javascript
+ Scenario: I compare branches with an open merge request
+ Given project have an open merge request
+ And I visit compare refs page
+ And I fill compare fields with branches
+ Then I see compared branches
+ And I should not see button to create a new merge request
+ And I should see button to the merge request
+
@javascript
Scenario: I compare refs
Given I visit compare refs page
diff --git a/features/project/commits/revert.feature b/features/project/commits/revert.feature
new file mode 100644
index 0000000000..7a2effafe0
--- /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 37cd53ee97..ca3f2771aa 100644
--- a/features/project/fork.feature
+++ b/features/project/fork.feature
@@ -25,3 +25,25 @@ Feature: Project Fork
Then I should see "New merge request"
And I click link "New merge request"
Then I should see the new merge request page for my namespace
+
+ Scenario: Viewing forks of a Project
+ Given I click link "Fork"
+ When I fork to my namespace
+ 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"
+ When I fork to my namespace
+ And I visit the forks page of the "Shop" project
+ Then I should see my fork on the list
+ And I should not see the other fork listed
+ And I should see a private fork notice
diff --git a/features/project/issues/award_emoji.feature b/features/project/issues/award_emoji.feature
index 9a06fdc2ee..2945bb3753 100644
--- a/features/project/issues/award_emoji.feature
+++ b/features/project/issues/award_emoji.feature
@@ -7,8 +7,18 @@ 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
Then I have award added
And I can remove it by clicking to icon
@@ -16,11 +26,13 @@ Feature: Award Emoji
@javascript
Scenario: I can see the list of emoji categories
Given I click to emoji-picker
+ Then The search field is focused
Then I can see the activity and food categories
@javascript
Scenario: I can search emoji
Given I click to emoji-picker
+ Then The search field is focused
And I search "hand"
Then I see search result for "hand"
diff --git a/features/project/issues/issues.feature b/features/project/issues/issues.feature
index ab234bc750..89af58dcef 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
@@ -51,6 +58,46 @@ Feature: Project Issues
Then I should see comment "XML attached"
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"
+ And I visit my project's home page
+ And I visit project "Shop" issues page
+ Then The list should be sorted by "Oldest updated"
+
+ @javascript
+ Scenario: Visiting Merge Requests after being sorted the list
+ Given I visit project "Shop" issues page
+ And I sort the list by "Oldest updated"
+ And I visit project "Shop" merge requests page
+ Then The list should be sorted by "Oldest updated"
+
+ @javascript
+ Scenario: Visiting Merge Requests from a differente Project after sorting
+ Given I visit project "Shop" merge requests page
+ And I sort the list by "Oldest updated"
+ And I visit dashboard merge requests page
+ 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"
diff --git a/features/project/merge_requests.feature b/features/project/merge_requests.feature
index aa9078b878..495f25f28e 100644
--- a/features/project/merge_requests.feature
+++ b/features/project/merge_requests.feature
@@ -39,6 +39,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"
@@ -75,6 +76,58 @@ Feature: Project Merge Requests
And I leave a comment like "XML attached"
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"
+ And I visit my project's home page
+ And I visit project "Shop" merge requests page
+ Then The list should be sorted by "Oldest updated"
+
+ @javascript
+ Scenario: Visiting Issues after being sorted the list
+ Given I visit project "Shop" merge requests page
+ And I sort the list by "Oldest updated"
+ And I visit project "Shop" issues page
+ Then The list should be sorted by "Oldest updated"
+
+ @javascript
+ Scenario: Visiting Merge Requests from a differente Project after sorting
+ Given I visit project "Shop" merge requests page
+ And I sort the list by "Oldest updated"
+ And I visit dashboard merge requests page
+ Then The list should be sorted by "Oldest updated"
+
+ @javascript
+ Scenario: Sort merge requests by upvotes/downvotes
+ Given project "Shop" have "Bug NS-05" open merge request with diffs inside
+ 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: Visiting Merge Requests after commenting on diffs
+ 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
+
@javascript
Scenario: I comment on a merge request diff
Given project "Shop" have "Bug NS-05" open merge request with diffs inside
@@ -83,6 +136,15 @@ Feature: Project Merge Requests
And I leave a comment like "Line is wrong" on diff
And I switch to the merge request's comments tab
Then I should see a discussion has started on diff
+ And I should see a badge of "1" next to the discussion link
+
+ @javascript
+ Scenario: I see a new comment on merge request diff from another user in the discussion tab
+ Given project "Shop" have "Bug NS-05" open merge request with diffs inside
+ And I visit merge request page "Bug NS-05"
+ And user "John Doe" leaves a comment like "Line is wrong" on diff
+ Then I should see a discussion by user "John Doe" has started on diff
+ And I should see a badge of "1" next to the discussion link
@javascript
Scenario: I edit a comment on a merge request diff
@@ -100,9 +162,11 @@ Feature: Project Merge Requests
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 should see a badge of "1" next to the discussion link
And I delete the comment "Line is wrong" on diff
And I click on the Discussion tab
Then I should not see any discussion
+ And I should see a badge of "0" next to the discussion link
@javascript
Scenario: I comment on a line of a commit in merge request
diff --git a/features/project/merge_requests/revert.feature b/features/project/merge_requests/revert.feature
new file mode 100644
index 0000000000..d767b08888
--- /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 0000000000..713f0f3b97
--- /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/source/browse_files.feature b/features/project/source/browse_files.feature
index a8c276b949..1e09dbc4c8 100644
--- a/features/project/source/browse_files.feature
+++ b/features/project/source/browse_files.feature
@@ -320,3 +320,13 @@ Feature: Project Source Browse Files
Then I should see download link and object size
And I should not see lfs pointer details
And I should see buttons for allowed commands
+
+ @javascript
+ Scenario: I preview an SVG file
+ Given I click on "Upload file" link in repo
+ And I upload a new SVG file
+ And I fill the upload file commit message
+ And I fill the new branch name
+ And I click on "Upload file"
+ Given I visit the SVG file
+ Then I can see the new rendered SVG image
diff --git a/features/steps/admin/appearance.rb b/features/steps/admin/appearance.rb
new file mode 100644
index 0000000000..0d1be46d11
--- /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/admin/broadcast_messages.rb b/features/steps/admin/broadcast_messages.rb
index 6cacdf4764..af2b4a2931 100644
--- a/features/steps/admin/broadcast_messages.rb
+++ b/features/steps/admin/broadcast_messages.rb
@@ -19,7 +19,7 @@ class Spinach::Features::AdminBroadcastMessages < Spinach::FeatureSteps
end
step 'submit form with new customized broadcast message' do
- fill_in 'broadcast_message_message', with: 'Application update from 4:00 CST to 5:00 CST'
+ fill_in 'broadcast_message_message', with: 'Application update from **4:00 CST to 5:00 CST**'
fill_in 'broadcast_message_color', with: '#f2dede'
fill_in 'broadcast_message_font', with: '#b94a48'
select Date.today.next_year.year, from: "broadcast_message_ends_at_1i"
@@ -28,6 +28,7 @@ class Spinach::Features::AdminBroadcastMessages < Spinach::FeatureSteps
step 'I should see a customized broadcast message' do
expect(page).to have_content 'Application update from 4:00 CST to 5:00 CST'
+ expect(page).to have_selector 'strong', text: '4:00 CST to 5:00 CST'
expect(page).to have_selector %(div[style="background-color: #f2dede; color: #b94a48"])
end
@@ -51,4 +52,15 @@ class Spinach::Features::AdminBroadcastMessages < Spinach::FeatureSteps
step 'I should not see the removed broadcast message' do
expect(page).not_to have_content 'Migration to new server'
end
+
+ step 'I enter a broadcast message with Markdown' do
+ fill_in 'broadcast_message_message', with: "Live **Markdown** previews. :tada:"
+ end
+
+ step 'I should see a live preview of the rendered broadcast message' do
+ page.within('.broadcast-message-preview') do
+ expect(page).to have_selector('strong', text: 'Markdown')
+ expect(page).to have_selector('img.emoji')
+ end
+ end
end
diff --git a/features/steps/admin/spam_logs.rb b/features/steps/admin/spam_logs.rb
new file mode 100644
index 0000000000..ad825fd414
--- /dev/null
+++ b/features/steps/admin/spam_logs.rb
@@ -0,0 +1,28 @@
+class Spinach::Features::AdminSpamLogs < Spinach::FeatureSteps
+ include SharedAuthentication
+ include SharedPaths
+ include SharedAdmin
+
+ step 'I should see list of spam logs' do
+ expect(page).to have_content('Spam Logs')
+ expect(page).to have_content spam_log.source_ip
+ expect(page).to have_content spam_log.noteable_type
+ expect(page).to have_content 'N'
+ expect(page).to have_content spam_log.title
+ expect(page).to have_content truncate(spam_log.description)
+ expect(page).to have_link('Remove user')
+ expect(page).to have_link('Block user')
+ end
+
+ step 'spam logs exist' do
+ create(:spam_log)
+ end
+
+ def spam_log
+ @spam_log ||= SpamLog.first
+ end
+
+ def truncate(description)
+ "#{spam_log.description[0...97]}..."
+ end
+end
diff --git a/features/steps/dashboard/dashboard.rb b/features/steps/dashboard/dashboard.rb
index 63f0ec2b6e..5062e34884 100644
--- a/features/steps/dashboard/dashboard.rb
+++ b/features/steps/dashboard/dashboard.rb
@@ -2,6 +2,7 @@ class Spinach::Features::Dashboard < Spinach::FeatureSteps
include SharedAuthentication
include SharedPaths
include SharedProject
+ include SharedIssuable
step 'I should see "New Project" link' do
expect(page).to have_link "New project"
diff --git a/features/steps/dashboard/todos.rb b/features/steps/dashboard/todos.rb
new file mode 100644
index 0000000000..9722a5a848
--- /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/groups.rb b/features/steps/groups.rb
index 4c5122d1b7..1e2a78a602 100644
--- a/features/steps/groups.rb
+++ b/features/steps/groups.rb
@@ -120,6 +120,10 @@ class Spinach::Features::Groups < Spinach::FeatureSteps
expect(page).to have_xpath("//span[@class='label label-warning']", text: 'archived')
end
+ step 'I visit group "NonExistentGroup" page' do
+ visit group_path(-1)
+ 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 b9ff6ae67f..0000000000
--- 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 0305f7e6da..6b0c1049ec 100644
--- a/features/steps/profile/profile.rb
+++ b/features/steps/profile/profile.rb
@@ -97,7 +97,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
diff --git a/features/steps/project/badges/build.rb b/features/steps/project/badges/build.rb
new file mode 100644
index 0000000000..47540f356e
--- /dev/null
+++ b/features/steps/project/badges/build.rb
@@ -0,0 +1,32 @@
+class Spinach::Features::ProjectBadgesBuild < Spinach::FeatureSteps
+ include SharedAuthentication
+ include SharedProject
+ include SharedBuilds
+ include RepoHelpers
+
+ step 'I display builds badge for a master branch' do
+ visit build_namespace_project_badges_path(@project.namespace, @project, ref: :master, format: :svg)
+ end
+
+ step 'I should see a build success badge' do
+ expect_badge('success')
+ end
+
+ step 'I should see a build failed badge' do
+ expect_badge('failed')
+ end
+
+ step 'I should see a build running badge' do
+ 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')
+ expect(svg.at(%Q{text:contains("#{status}")})).to be_truthy
+ end
+end
diff --git a/features/steps/project/builds/artifacts.rb b/features/steps/project/builds/artifacts.rb
index 25f2f4e837..1bdb57af9d 100644
--- a/features/steps/project/builds/artifacts.rb
+++ b/features/steps/project/builds/artifacts.rb
@@ -73,4 +73,14 @@ class Spinach::Features::ProjectBuildsArtifacts < Spinach::FeatureSteps
expect(response_json[:archive]).to end_with('build_artifacts.zip')
expect(response_json[:entry]).to eq Base64.encode64('ci_artifacts.txt')
end
+
+ step 'I click a first row within build artifacts table' do
+ row = first('tr[data-link]')
+ @row_path = row['data-link']
+ row.click
+ end
+
+ step 'page with a coresponding path is loading' do
+ expect(current_path).to eq @row_path
+ end
end
diff --git a/features/steps/project/builds/summary.rb b/features/steps/project/builds/summary.rb
index 2439d48fbe..4688a0e209 100644
--- a/features/steps/project/builds/summary.rb
+++ b/features/steps/project/builds/summary.rb
@@ -4,11 +4,30 @@ class Spinach::Features::ProjectBuildsSummary < Spinach::FeatureSteps
include SharedBuilds
include RepoHelpers
- step 'I see summary for build' do
- expect(page).to have_content "Build ##{@build.id}"
+ 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 see build trace' do
- expect(page).to have_css '#build-trace'
+ 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 daf6cdaaac..f9fd733246 100644
--- a/features/steps/project/commits/commits.rb
+++ b/features/steps/project/commits/commits.rb
@@ -33,6 +33,13 @@ class Spinach::Features::ProjectCommits < Spinach::FeatureSteps
expect(page).to have_content "Showing #{sample_commit.files_changed_count} changed files"
end
+ step 'I fill compare fields with branches' do
+ fill_in 'from', with: 'feature'
+ fill_in 'to', with: 'master'
+
+ click_button 'Compare'
+ end
+
step 'I fill compare fields with refs' do
fill_in "from", with: sample_commit.parent_id
fill_in "to", with: sample_commit.id
@@ -56,6 +63,56 @@ class Spinach::Features::ProjectCommits < Spinach::FeatureSteps
expect(page).to have_content "Showing 2 changed files"
end
+ step 'I visit commits list page for feature branch' do
+ visit namespace_project_commits_path(@project.namespace, @project, 'feature', { limit: 5 })
+ end
+
+ step 'I see feature branch commits' do
+ commit = @project.repository.commit('0b4bc9a')
+ expect(page).to have_content(@project.name)
+ expect(page).to have_content(commit.message[0..12])
+ expect(page).to have_content(commit.short_id)
+ end
+
+ step 'project have an open merge request' do
+ create(:merge_request,
+ title: 'Feature',
+ source_project: @project,
+ source_branch: 'feature',
+ target_branch: 'master',
+ author: @project.users.first
+ )
+ end
+
+ step 'I click the "Compare" tab' do
+ click_link('Compare')
+ end
+
+ step 'I fill compare fields with branches' do
+ fill_in 'from', with: 'master'
+ fill_in 'to', with: 'feature'
+
+ click_button 'Compare'
+ end
+
+ step 'I see compared branches' do
+ expect(page).to have_content 'Commits (1)'
+ expect(page).to have_content 'Showing 1 changed file with 5 additions and 0 deletions'
+ end
+
+ step 'I see button to create a new merge request' do
+ expect(page).to have_link 'Create Merge Request'
+ end
+
+ step 'I should not see button to create a new merge request' do
+ expect(page).to_not have_link 'Create Merge Request'
+ end
+
+ step 'I should see button to the merge request' do
+ merge_request = MergeRequest.find_by(title: 'Feature')
+ expect(page).to have_link "View Open Merge Request", href: namespace_project_merge_request_path(@project.namespace, @project, merge_request)
+ end
+
step 'I see breadcrumb links' do
expect(page).to have_selector('ul.breadcrumb')
expect(page).to have_selector('ul.breadcrumb a', count: 4)
diff --git a/features/steps/project/commits/revert.rb b/features/steps/project/commits/revert.rb
new file mode 100644
index 0000000000..94a5d4e2e4
--- /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 e98bd51ca8..527f7853da 100644
--- a/features/steps/project/fork.rb
+++ b/features/steps/project/fork.rb
@@ -49,4 +49,35 @@ class Spinach::Features::ProjectFork < Spinach::FeatureSteps
step 'I should see the new merge request page for my namespace' do
current_path.should have_content(/#{current_user.namespace.name}/i)
end
+
+ step 'I visit the forks page of the "Shop" project' do
+ @project = Project.where(name: 'Shop').last
+ visit namespace_project_forks_path(@project.namespace, @project)
+ end
+
+ step 'I should see my fork on the list' do
+ page.within('.projects-list-holder') do
+ project = @user.fork_of(@project)
+ expect(page).to have_content("#{project.namespace.human_name} / #{project.name}")
+ 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
+ end
+
+ step 'I should not see the other fork listed' do
+ expect(page).not_to have_content("#{@forked_project.namespace.human_name} / #{@forked_project.name}")
+ end
+
+ step 'I should see a private fork notice' do
+ expect(page).to have_content("1 private fork")
+ end
end
diff --git a/features/steps/project/forked_merge_requests.rb b/features/steps/project/forked_merge_requests.rb
index cbdce78dc0..7e4425ff66 100644
--- a/features/steps/project/forked_merge_requests.rb
+++ b/features/steps/project/forked_merge_requests.rb
@@ -43,7 +43,9 @@ class Spinach::Features::ProjectForkedMergeRequests < Spinach::FeatureSteps
expect(page).to have_css("h3.page-title", text: "New Merge Request")
- fill_in "merge_request_title", with: "Merge Request On Forked Project"
+ page.within 'form#new_merge_request' do
+ fill_in "merge_request_title", with: "Merge Request On Forked Project"
+ end
end
step 'I submit the merge request' do
diff --git a/features/steps/project/issues/award_emoji.rb b/features/steps/project/issues/award_emoji.rb
index 2c2ed08655..93cf608cc6 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
@@ -66,4 +94,8 @@ class Spinach::Features::AwardEmoji < Spinach::FeatureSteps
expect(page).to have_selector '[data-emoji="raised_hand"]'
end
end
+
+ step 'The search field is focused' do
+ page.evaluate_script("document.activeElement.id").should eq "emoji_search"
+ end
end
diff --git a/features/steps/project/issues/issues.rb b/features/steps/project/issues/issues.rb
index 8e8c9c5745..565bf088b4 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
@@ -293,7 +354,13 @@ class Spinach::Features::ProjectIssues < Spinach::FeatureSteps
expect(page).to have_content('Yay!')
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/merge_requests.rb b/features/steps/project/merge_requests.rb
index 28f87a9bea..dde864f518 100644
--- a/features/steps/project/merge_requests.rb
+++ b/features/steps/project/merge_requests.rb
@@ -138,6 +138,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'
@@ -181,6 +231,15 @@ class Spinach::Features::ProjectMergeRequests < Spinach::FeatureSteps
leave_comment "Line is wrong"
end
+ step 'user "John Doe" leaves a comment like "Line is wrong" on diff' do
+ mr = MergeRequest.find_by(title: "Bug NS-05")
+ create(:note_on_merge_request_diff, project: project,
+ noteable_id: mr.id,
+ author: user_exists("John Doe"),
+ line_code: sample_commit.line_code,
+ note: 'Line is wrong')
+ end
+
step 'I leave a comment like "Line is wrong" on diff in commit' do
click_diff_line(sample_commit.line_code)
leave_comment "Line is wrong"
@@ -238,6 +297,22 @@ class Spinach::Features::ProjectMergeRequests < Spinach::FeatureSteps
end
end
+ step 'I should see a discussion by user "John Doe" has started on diff' do
+ page.within(".notes .discussion") do
+ page.should have_content "#{user_exists("John Doe").name} started a discussion"
+ page.should have_content sample_commit.line_code_path
+ page.should have_content "Line is wrong"
+ end
+ end
+
+ step 'I should see a badge of "1" next to the discussion link' do
+ expect_discussion_badge_to_have_counter("1")
+ end
+
+ step 'I should see a badge of "0" next to the discussion link' do
+ expect_discussion_badge_to_have_counter("0")
+ end
+
step 'I should see a discussion has started on commit diff' do
page.within(".notes .discussion") do
page.should have_content "#{current_user.name} started a discussion on commit"
@@ -415,6 +490,14 @@ 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")
+ 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")
+ end
+
def merge_request
@merge_request ||= MergeRequest.find_by!(title: "Bug NS-05")
end
@@ -444,4 +527,10 @@ class Spinach::Features::ProjectMergeRequests < Spinach::FeatureSteps
def have_visible_content (text)
have_css("*", text: text, visible: true)
end
+
+ def expect_discussion_badge_to_have_counter(value)
+ page.within(".notes-tab .badge") do
+ page.should have_content value
+ end
+ end
end
diff --git a/features/steps/project/merge_requests/revert.rb b/features/steps/project/merge_requests/revert.rb
new file mode 100644
index 0000000000..c5a4cfce6f
--- /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, user: @user, project: @project, access_level: ProjectMember::DEVELOPER)
+ @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_milestone.rb b/features/steps/project/project_milestone.rb
new file mode 100644
index 0000000000..2508c09e36
--- /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/browse_files.rb b/features/steps/project/source/browse_files.rb
index d08935aa10..51b1579167 100644
--- a/features/steps/project/source/browse_files.rb
+++ b/features/steps/project/source/browse_files.rb
@@ -52,7 +52,7 @@ class Spinach::Features::ProjectSourceBrowseFiles < Spinach::FeatureSteps
end
step 'I should see raw file content' do
- expect(source).to eq sample_blob.data
+ expect(source).to eq '' # Body is filled in by gitlab-workhorse
end
step 'I click button "Edit"' do
@@ -351,6 +351,19 @@ class Spinach::Features::ProjectSourceBrowseFiles < Spinach::FeatureSteps
expect(page).to have_content "You're not allowed to make changes to this project directly. A fork of this project has been created that you can make changes in, so you can submit a merge request."
end
+ # SVG files
+ step 'I upload a new SVG file' do
+ drop_in_dropzone test_svg_file
+ end
+
+ step 'I visit the SVG file' do
+ visit namespace_project_blob_path(@project.namespace, @project, 'new_branch_name/logo_sample.svg')
+ end
+
+ step 'I can see the new rendered SVG image' do
+ expect(find('.file-content')).to have_css('img')
+ end
+
private
def set_new_content
@@ -410,4 +423,8 @@ class Spinach::Features::ProjectSourceBrowseFiles < Spinach::FeatureSteps
def test_image_file
File.join(Rails.root, 'spec', 'fixtures', 'banana_sample.gif')
end
+
+ def test_svg_file
+ File.join(Rails.root, 'spec', 'fixtures', 'logo_sample.svg')
+ end
end
diff --git a/features/steps/project/source/markdown_render.rb b/features/steps/project/source/markdown_render.rb
index 3a4f7a6e01..2134dae168 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 d753ae1459..223b7277b5 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,11 +159,13 @@ 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
- fill_in "Search in this project", with: "wiki_content"
+ fill_in "Search", with: "wiki_content"
click_button "Search"
end
diff --git a/features/steps/shared/builds.rb b/features/steps/shared/builds.rb
index f88b01af84..0bd5d93b99 100644
--- a/features/steps/shared/builds.rb
+++ b/features/steps/shared/builds.rb
@@ -6,14 +6,30 @@ module SharedBuilds
end
step 'project has a recent build' do
- ci_commit = create :ci_commit, project: @project, sha: sample_commit.id
- @build = create :ci_build, commit: ci_commit
+ @ci_commit = create(:ci_commit, project: @project, sha: @project.commit.sha)
+ @build = create(:ci_build, commit: @ci_commit)
end
- step 'I visit recent build summary page' do
+ step 'recent build is successful' do
+ @build.update_column(:status, 'success')
+ end
+
+ step 'recent build failed' do
+ @build.update_column(:status, 'failed')
+ end
+
+ step 'project has another build that is running' do
+ create(:ci_build, commit: @ci_commit, name: 'second build', status: 'running')
+ end
+
+ step 'I visit recent build details page' do
visit namespace_project_build_path(@project.namespace, @project, @build)
end
+ step 'I visit project builds page' do
+ visit namespace_project_builds_path(@project.namespace, @project)
+ end
+
step 'recent build has artifacts available' do
artifacts = Rails.root + 'spec/fixtures/ci_build_artifacts.zip'
archive = fixture_file_upload(artifacts, 'application/zip')
@@ -26,6 +42,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'
@@ -34,4 +54,21 @@ module SharedBuilds
step 'I access artifacts download page' do
visit download_namespace_project_build_artifacts_path(@project.namespace, @project, @build)
end
+
+ step 'I see details of a build' do
+ expect(page).to have_content "Build ##{@build.id}"
+ end
+
+ step 'I see build trace' do
+ expect(page).to have_css '#build-trace'
+ end
+
+ step 'I see the build' do
+ page.within('.commit_status') do
+ expect(page).to have_content "##{@build.id}"
+ expect(page).to have_content @build.sha[0..7]
+ expect(page).to have_content @build.ref
+ expect(page).to have_content @build.name
+ end
+ end
end
diff --git a/features/steps/shared/diff_note.rb b/features/steps/shared/diff_note.rb
index c6a0ae2ba3..06e6944189 100644
--- a/features/steps/shared/diff_note.rb
+++ b/features/steps/shared/diff_note.rb
@@ -23,7 +23,7 @@ module SharedDiffNote
page.within(diff_file_selector) do
click_diff_line(sample_commit.line_code)
- page.within("form[rel$='#{sample_commit.line_code}']") do
+ page.within("form[id$='#{sample_commit.line_code}']") do
fill_in "note[note]", with: "Typo, please fix"
find(".js-comment-button").trigger("click")
sleep 0.05
@@ -33,7 +33,7 @@ module SharedDiffNote
step 'I leave a diff comment in a parallel view on the left side like "Old comment"' do
click_parallel_diff_line(sample_commit.line_code, 'old')
- page.within("#{diff_file_selector} form[rel$='#{sample_commit.line_code}']") do
+ page.within("#{diff_file_selector} form[id$='#{sample_commit.line_code}']") do
fill_in "note[note]", with: "Old comment"
find(".js-comment-button").trigger("click")
end
@@ -41,7 +41,7 @@ module SharedDiffNote
step 'I leave a diff comment in a parallel view on the right side like "New comment"' do
click_parallel_diff_line(sample_commit.line_code, 'new')
- page.within("#{diff_file_selector} form[rel$='#{sample_commit.line_code}']") do
+ page.within("#{diff_file_selector} form[id$='#{sample_commit.line_code}']") do
fill_in "note[note]", with: "New comment"
find(".js-comment-button").trigger("click")
end
@@ -51,7 +51,7 @@ module SharedDiffNote
page.within(diff_file_selector) do
click_diff_line(sample_commit.line_code)
- page.within("form[rel$='#{sample_commit.line_code}']") do
+ page.within("form[id$='#{sample_commit.line_code}']") do
fill_in "note[note]", with: "Should fix it :smile:"
find('.js-md-preview-button').click
end
@@ -62,7 +62,7 @@ module SharedDiffNote
page.within(diff_file_selector) do
click_diff_line(sample_commit.del_line_code)
- page.within("form[rel$='#{sample_commit.del_line_code}']") do
+ page.within("form[id$='#{sample_commit.del_line_code}']") do
fill_in "note[note]", with: "DRY this up"
find('.js-md-preview-button').click
end
@@ -91,7 +91,7 @@ module SharedDiffNote
page.within(diff_file_selector) do
click_diff_line(sample_commit.line_code)
- page.within("form[rel$='#{sample_commit.line_code}']") do
+ page.within("form[id$='#{sample_commit.line_code}']") do
fill_in 'note[note]', with: ':smile:'
click_button('Add Comment')
end
diff --git a/features/steps/shared/issuable.rb b/features/steps/shared/issuable.rb
index 4c5f7488ef..ae10c6069a 100644
--- a/features/steps/shared/issuable.rb
+++ b/features/steps/shared/issuable.rb
@@ -106,6 +106,53 @@ module SharedIssuable
edit_issuable
end
+ step 'I sort the list by "Oldest updated"' do
+ find('button.dropdown-toggle.btn').click
+ page.within('ul.dropdown-menu.dropdown-menu-align-right li') do
+ click_link "Oldest updated"
+ 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)
@@ -146,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 444d6726f9..eb6df61b8e 100644
--- a/features/steps/shared/note.rb
+++ b/features/steps/shared/note.rb
@@ -144,4 +144,11 @@ 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 4264c9c6f1..4478d418d8 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
@@ -191,6 +203,10 @@ module SharedPaths
visit admin_application_settings_path
end
+ step 'I visit spam logs page' do
+ visit admin_spam_logs_path
+ end
+
step 'I visit applications page' do
visit admin_applications_path
end
diff --git a/features/steps/shared/project.rb b/features/steps/shared/project.rb
index d9c75d1223..b13e82f276 100644
--- a/features/steps/shared/project.rb
+++ b/features/steps/shared/project.rb
@@ -240,6 +240,18 @@ module SharedProject
end
end
+ step 'The project is internal' do
+ @project.update(visibility_level: Gitlab::VisibilityLevel::INTERNAL)
+ end
+
+ step 'public access for builds is enabled' do
+ @project.update(public_builds: true)
+ end
+
+ step 'public access for builds is disabled' do
+ @project.update(public_builds: false)
+ end
+
def user_owns_project(user_name:, project_name:, visibility: :private)
user = user_exists(user_name, username: user_name.gsub(/\s/, '').underscore)
project = Project.find_by(name: project_name)
diff --git a/features/support/capybara.rb b/features/support/capybara.rb
index 4156c7ec48..f33379f76c 100644
--- a/features/support/capybara.rb
+++ b/features/support/capybara.rb
@@ -6,11 +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)
-end
-
-Spinach.hooks.on_tag("javascript") do
- Capybara.current_driver = Capybara.javascript_driver
+ Capybara::Poltergeist::Driver.new(app, js_errors: true, timeout: timeout, window_size: [1366, 768])
end
Capybara.default_wait_time = timeout
@@ -22,3 +18,7 @@ unless ENV['CI'] || ENV['CI_SERVER']
# Keep only the screenshots generated from the last failing test suite
Capybara::Screenshot.prune_strategy = :keep_last_run
end
+
+Spinach.hooks.before_run do
+ TestEnv.warm_asset_cache
+end
diff --git a/fixtures/emojis/aliases.json b/fixtures/emojis/aliases.json
index 547ce7978b..d3831d8045 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 0000000000..8838fb9a3a
--- /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 60ef2399e1..7f204c1a8e 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": [
+ ":small_airplane:"
+ ],
"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_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": [
+ ":left_anger_bubble:"
+ ],
"aliases_ascii": [],
- "keywords": ["speech", "balloon", "talk", "mood", "conversation", "communication", "comic", "angry"]
+ "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": [
+ ":right_anger_bubble:"
+ ],
"aliases_ascii": [],
- "keywords": ["speech", "balloon", "talk", "mood", "conversation", "communication", "comic", "angry"]
+ "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": [
+ ":beach_with_umbrella:"
+ ],
"aliases_ascii": [],
- "keywords": ["sand", "sun", "surf", "vacation", "relaxation", "tanning", "tan", "swimming"]
+ "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": [
+ ":pocket calculator:"
+ ],
"aliases_ascii": [],
- "keywords": ["add", "subtract", "multiple", "divide", "scientific"]
+ "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": [
+ ":tape_cartridge:"
+ ],
"aliases_ascii": [],
- "keywords": ["oldschool", "save", "technology", "disk", "storage", "information", "computer", "drive", "megabyte"]
+ "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's"],
+ "keywords": [
+ "desert",
+ "food",
+ "snack",
+ "chocolate",
+ "bar",
+ "candy",
+ "coca",
+ "hershey'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": [
+ ":city_sunrise:"
+ ],
"aliases_ascii": [],
- "keywords": ["photo", "city", "scape", "sunrise", "dawn", "light", "morning", "metropolitan", "rise", "sun"],
+ "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": [
+ ":couch_and_lamp:"
+ ],
"aliases_ascii": [],
- "keywords": ["lounge", "sectional", "sofa", "loveseat", "leather", "microfiber", "sit", "relax"]
+ "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": [
+ ":couple_with_heart_mm:"
+ ],
"aliases_ascii": [],
- "keywords": ["affection", "dating", "human", "like", "love", "marriage", "valentines"]
+ "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": [
+ ":couple_with_heart_ww:"
+ ],
"aliases_ascii": [],
- "keywords": ["affection", "dating", "human", "like", "love", "marriage", "valentines"]
+ "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": [
+ ":email:"
+ ],
"aliases_ascii": [],
- "keywords": ["communication", "inbox"],
+ "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": [
+ ":flame:"
+ ],
"aliases_ascii": [],
- "keywords": ["cook", "hot", "flame"],
+ "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": [
+ ":oncoming_fire_engine:"
+ ],
"aliases_ascii": [],
- "keywords": ["transportation", "vehicle", "fighter", "truck", "emergency"]
+ "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"]
+ "keywords": [
+ "country",
+ "nation",
+ "ao"
+ ]
+ },
+ "flag_aq": {
+ "unicode": "1F1E6-1F1F6",
+ "unicode_alternates": "",
+ "name": "antarctica",
+ "shortname": ":flag_aq:",
+ "category": "flags",
+ "aliases": [
+ ":aq:"
+ ],
+ "aliases_ascii": [],
+ "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"]
+ "keywords": [
+ "country",
+ "nation",
+ "ar"
+ ]
+ },
+ "flag_as": {
+ "unicode": "1F1E6-1F1F8",
+ "unicode_alternates": "",
+ "name": "american samoa",
+ "shortname": ":flag_as:",
+ "category": "flags",
+ "aliases": [
+ ":as:"
+ ],
+ "aliases_ascii": [],
+ "keywords": []
},
"flag_at": {
"unicode": "1F1E6-1F1F9",
@@ -4152,9 +8547,17 @@
"name": "austria",
"shortname": ":flag_at:",
"category": "flags",
- "aliases": [":at:"],
+ "aliases": [
+ ":at:"
+ ],
"aliases_ascii": [],
- "keywords": ["country", "nation", "österreich", "osterreich", "at"]
+ "keywords": [
+ "country",
+ "nation",
+ "ö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"]
+ "keywords": [
+ "country",
+ "nation",
+ "aw"
+ ]
+ },
+ "flag_ax": {
+ "unicode": "1F1E6-1F1FD",
+ "unicode_alternates": "",
+ "name": "åland islands",
+ "shortname": ":flag_ax:",
+ "category": "flags",
+ "aliases": [
+ ":ax:"
+ ],
+ "aliases_ascii": [],
+ "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": [
+ ":be:"
+ ],
"aliases_ascii": [],
- "keywords": ["country", "nation", "belgique", "belgie", "be"]
+ "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"]
+ "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": []
},
"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"]
+ "keywords": [
+ "country",
+ "nation",
+ "bo"
+ ]
+ },
+ "flag_bq": {
+ "unicode": "1F1E7-1F1F6",
+ "unicode_alternates": "",
+ "name": "caribbean netherlands",
+ "shortname": ":flag_bq:",
+ "category": "flags",
+ "aliases": [
+ ":bq:"
+ ],
+ "aliases_ascii": [],
+ "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"]
+ "keywords": [
+ "country",
+ "nation",
+ "bt"
+ ]
+ },
+ "flag_bv": {
+ "unicode": "1F1E7-1F1FB",
+ "unicode_alternates": "",
+ "name": "bouvet island",
+ "shortname": ":flag_bv:",
+ "category": "flags",
+ "aliases": [
+ ":bv:"
+ ],
+ "aliases_ascii": [],
+ "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"]
+ "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": []
},
"flag_cd": {
"unicode": "1F1E8-1F1E9",
@@ -4392,9 +8999,17 @@
"name": "the democratic republic of the congo",
"shortname": ":flag_cd:",
"category": "flags",
- "aliases": [":congo:"],
+ "aliases": [
+ ":congo:"
+ ],
"aliases_ascii": [],
- "keywords": ["country", "nation", "république démocratique du congo", "republique democratique du congo", "cd"]
+ "keywords": [
+ "country",
+ "nation",
+ "république dé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"]
+ "keywords": [
+ "country",
+ "nation",
+ "ci"
+ ]
+ },
+ "flag_ck": {
+ "unicode": "1F1E8-1F1F0",
+ "unicode_alternates": "",
+ "name": "cook islands",
+ "shortname": ":flag_ck:",
+ "category": "flags",
+ "aliases": [
+ ":ck:"
+ ],
+ "aliases_ascii": [],
+ "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": [
+ ":cn:"
+ ],
"aliases_ascii": [],
- "keywords": ["chinese", "prc", "zhong guo", "country", "nation", "cn"]
+ "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"]
+ "keywords": [
+ "country",
+ "nation",
+ "co"
+ ]
+ },
+ "flag_cp": {
+ "unicode": "1F1E8-1F1F5",
+ "unicode_alternates": "",
+ "name": "clipperton island",
+ "shortname": ":flag_cp:",
+ "category": "flags",
+ "aliases": [
+ ":cp:"
+ ],
+ "aliases_ascii": [],
+ "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"]
+ "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": []
},
"flag_cy": {
"unicode": "1F1E8-1F1FE",
@@ -4512,9 +9245,17 @@
"name": "cyprus",
"shortname": ":flag_cy:",
"category": "flags",
- "aliases": [":cy:"],
+ "aliases": [
+ ":cy:"
+ ],
"aliases_ascii": [],
- "keywords": ["country", "nation", "kibris", "kypros", "cy"]
+ "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"]
+ "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": []
},
"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"]
+ "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": []
},
"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": [
+ ":eh:"
+ ],
"aliases_ascii": [],
- "keywords": ["country", "nation", "aṣ-Ṣaḥrā’ al-gharbīyah", "sahra", "gharbiyah", "eh"]
+ "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": [
+ ":es:"
+ ],
"aliases_ascii": [],
- "keywords": ["nation", "españa", "country", "espana", "es"]
+ "keywords": [
+ "nation",
+ "españ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"]
+ "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": []
},
"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": [
+ ":gb:"
+ ],
"aliases_ascii": [],
- "keywords": ["UK", "gb", "britsh", "nation", "united kingdom", "england", "country"]
+ "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"]
+ "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": []
},
"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"]
+ "keywords": [
+ "country",
+ "nation",
+ "guinee",
+ "gn"
+ ]
+ },
+ "flag_gp": {
+ "unicode": "1F1EC-1F1F5",
+ "unicode_alternates": "",
+ "name": "guadeloupe",
+ "shortname": ":flag_gp:",
+ "category": "flags",
+ "aliases": [
+ ":gp:"
+ ],
+ "aliases_ascii": [],
+ "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"]
+ "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": []
},
"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": [
+ ":gw:"
+ ],
"aliases_ascii": [],
- "keywords": ["country", "nation", "guine-bissau", "guine bissau", "gw"]
+ "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"]
+ "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": []
},
"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"]
+ "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": []
},
"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": [
+ ":ie:"
+ ],
"aliases_ascii": [],
- "keywords": ["country", "nation", "éire", "eire", "ie"]
+ "keywords": [
+ "country",
+ "nation",
+ "é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"]
+ "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": []
},
"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"]
+ "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": []
},
"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": [
+ ":ki:"
+ ],
"aliases_ascii": [],
- "keywords": ["country", "nation", "kiribati", "kiribas", "ki"]
+ "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": [
+ ":lu:"
+ ],
"aliases_ascii": [],
- "keywords": ["country", "nation", "luxembourg", "letzebuerg", "lu"]
+ "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"]
+ "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": []
},
"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"]
+ "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": []
+ },
+ "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": [
+ ":nc:"
+ ],
"aliases_ascii": [],
- "keywords": ["country", "nation", "nouvelle", "calédonie", "caledonie", "nc"]
+ "keywords": [
+ "country",
+ "nation",
+ "nouvelle",
+ "calé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"]
+ "keywords": [
+ "country",
+ "nation",
+ "ne"
+ ]
+ },
+ "flag_nf": {
+ "unicode": "1F1F3-1F1EB",
+ "unicode_alternates": "",
+ "name": "norfolk island",
+ "shortname": ":flag_nf:",
+ "category": "flags",
+ "aliases": [
+ ":nf:"
+ ],
+ "aliases_ascii": [],
+ "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": [
+ ":nl:"
+ ],
"aliases_ascii": [],
- "keywords": ["country", "nation", "nederland", "holland", "nl"]
+ "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": [
+ ":pf:"
+ ],
"aliases_ascii": [],
- "keywords": ["country", "nation", "polynésie française", "polynesie francaise", "pf"]
+ "keywords": [
+ "country",
+ "nation",
+ "polynésie franç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"]
+ "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": []
},
"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"]
+ "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": []
},
"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": [
+ ":saudiarabia:",
+ ":saudi:"
+ ],
"aliases_ascii": [],
- "keywords": ["country", "nation", "al arabiyah as suudiyah", "sa"]
+ "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"]
+ "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": []
},
"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"]
+ "keywords": [
+ "country",
+ "nation",
+ "sr"
+ ]
+ },
+ "flag_ss": {
+ "unicode": "1F1F8-1F1F8",
+ "unicode_alternates": "",
+ "name": "south sudan",
+ "shortname": ":flag_ss:",
+ "category": "flags",
+ "aliases": [
+ ":ss:"
+ ],
+ "aliases_ascii": [],
+ "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"]
+ "keywords": [
+ "country",
+ "nation",
+ "sv"
+ ]
+ },
+ "flag_sx": {
+ "unicode": "1F1F8-1F1FD",
+ "unicode_alternates": "",
+ "name": "sint maarten",
+ "shortname": ":flag_sx:",
+ "category": "flags",
+ "aliases": [
+ ":sx:"
+ ],
+ "aliases_ascii": [],
+ "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"]
+ "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": []
+ },
+ "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"]
+ "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": []
},
"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"]
+ "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": []
},
"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"]
+ "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": []
},
"flag_us": {
"unicode": "1F1FA-1F1F8",
@@ -6082,9 +12168,20 @@
"name": "united states",
"shortname": ":flag_us:",
"category": "flags",
- "aliases": [":us:"],
+ "aliases": [
+ ":us:"
+ ],
"aliases_ascii": [],
- "keywords": ["american", "country", "nation", "usa", "united states of america", "america", "old glory", "us"]
+ "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"]
+ "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": []
},
"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"]
+ "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": []
},
"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": [
+ ":black_hard_shell_floppy_disk:"
+ ],
"aliases_ascii": [],
- "keywords": ["oldschool", "save", "technology", "storage", "information", "computer", "drive", "megabyte"]
+ "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": [
+ ":white_hard_shell_floppy_disk:"
+ ],
"aliases_ascii": [],
- "keywords": ["oldschool", "save", "technology", "storage", "information", "computer", "drive", "megabyte"]
+ "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": [
+ ":fork_and_knife_with_plate:"
+ ],
"aliases_ascii": [],
- "keywords": ["meal", "food", "breakfast", "lunch", "dinner", "utensils", "setting"]
+ "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": [
+ ":anguished:"
+ ],
"aliases_ascii": [],
- "keywords": ["aw", "face", "frown", "sad", "pout", "sulk", "glower"],
+ "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"]
+ "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"
+ ]
},
"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": [
+ ":house_buildings:"
+ ],
"aliases_ascii": [],
- "keywords": ["home", "residence", "dwelling", "mansion", "bungalow", "ranch", "craftsman"]
+ "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": [
+ ":derelict_house_building:"
+ ],
"aliases_ascii": [],
- "keywords": ["home", "residence", "dwelling", "mansion", "bungalow", "ranch", "craftsman", "boarded", "abandoned", "vacant", "run down", "shoddy"]
+ "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's", "levi", "designer", "work", "skinny"],
+ "keywords": [
+ "fashion",
+ "shopping",
+ "jeans",
+ "pants",
+ "blue",
+ "denim",
+ "levi'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": [
+ ":couplekiss_mm:"
+ ],
"aliases_ascii": [],
- "keywords": ["dating", "like", "love", "marriage", "valentines", "couple"]
+ "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": [
+ ":couplekiss_ww:"
+ ],
"aliases_ascii": [],
- "keywords": ["dating", "like", "love", "marriage", "valentines", "couple"]
+ "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"]
+ "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"
+ ]
},
"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": [
+ ":sports_medal:"
+ ],
"aliases_ascii": [],
- "keywords": ["award", "ceremony", "contest", "ftw", "place", "win", "first", "show", "reward", "achievement"]
+ "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"]
+ "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"
+ ]
},
"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": [
+ ":lightning_mood_bubble:"
+ ],
"aliases_ascii": [],
- "keywords": ["balloon", "conversation", "communication", "comic", "feeling"]
+ "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"]
+ "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": [
+ "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": [
+ ":rolled_up_newspaper:"
+ ],
"aliases_ascii": [],
- "keywords": ["headline", "press"]
+ "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't", "not"],
+ "keywords": [
+ "female",
+ "girl",
+ "woman",
+ "no",
+ "stop",
+ "nope",
+ "don'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": [
+ ":grandma:"
+ ],
"aliases_ascii": [],
- "keywords": ["female", "girl", "women", "grandma", "grandmother"],
+ "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": [
+ ":optical_disc_icon:"
+ ],
"aliases_ascii": [],
- "keywords": ["cd", "dvd", "disc", "disk", "technology"]
+ "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": [
+ ":national_park:"
+ ],
"aliases_ascii": [],
- "keywords": ["woods", "nature", "wildlife", "forest", "wilderness", "national"]
+ "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": [
+ ":memo:"
+ ],
"aliases_ascii": [],
- "keywords": ["documents", "paper", "station", "write"],
+ "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": [
+ ":shit:",
+ ":hankey:",
+ ":poo:"
+ ],
"aliases_ascii": [],
- "keywords": ["poop", "shit", "shitface", "turd", "poo"],
+ "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": [
+ ":prohibited_sign:"
+ ],
"aliases_ascii": [],
- "keywords": ["no", "not", "denied", "disallow", "forbid", "limit", "stop"]
+ "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": [
+ ":film_projector:"
+ ],
"aliases_ascii": [],
- "keywords": ["movie", "video", "motion", "picture", "8mm", "16mm"]
+ "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": [
+ ":racing_car:"
+ ],
"aliases_ascii": [],
- "keywords": ["formula 1", "race", "stock", "nascar", "speed", "drive"]
+ "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": [
+ ":railroad_track:"
+ ],
"aliases_ascii": [],
- "keywords": ["train", "trolley", "subway", "locomotive", "transit"]
+ "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": [
+ ":skeleton:"
+ ],
"aliases_ascii": [],
- "keywords": ["dead", "skeleton", "dying"],
+ "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": [
+ ":left_speech_bubble:"
+ ],
"aliases_ascii": [],
- "keywords": ["balloon", "words", "talk", "conversation", "communication", "comic", "dialogue"]
+ "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": [
+ ":right_speech_bubble:"
+ ],
"aliases_ascii": [],
- "keywords": ["balloon", "words", "talk", "conversation", "communication", "comic", "dialogue"]
+ "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": [
+ ":three_speech_bubbles:"
+ ],
"aliases_ascii": [],
- "keywords": ["balloon", "words", "talk", "conversation", "communication", "comic", "dialogue"]
+ "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": [
+ ":two_speech_bubbles:"
+ ],
"aliases_ascii": [],
- "keywords": ["balloon", "words", "talk", "conversation", "communication", "comic", "dialogue"]
+ "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"]
+ "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",
+ "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": [
+ ":portable_stereo:"
+ ],
"aliases_ascii": [],
- "keywords": ["communication", "music", "program", "boom", "box"]
+ "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": [
+ ":left_thought_bubble:"
+ ],
"aliases_ascii": [],
- "keywords": ["balloon", "cloud", "comic", "think", "day dream", "wonder"]
+ "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": [
+ ":right_thought_bubble:"
+ ],
"aliases_ascii": [],
- "keywords": ["balloon", "cloud", "comic", "think", "day dream", "wonder"]
+ "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": [
+ ":reversed_thumbs_up_sign:"
+ ],
"aliases_ascii": [],
- "keywords": ["cool", "hand", "like", "yes", "+1"]
+ "keywords": [
+ "cool",
+ "hand",
+ "like",
+ "yes",
+ "+1"
+ ]
},
"thumbsdown": {
"unicode": "1F44E",
@@ -12158,22 +26811,219 @@
"name": "thumbs down sign",
"shortname": ":thumbsdown:",
"category": "emoticons",
- "aliases": [":-1:"],
+ "aliases": [
+ ":-1:"
+ ],
"aliases_ascii": [],
- "keywords": ["hand", "no"],
+ "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": [
+ ":+1:"
+ ],
"aliases_ascii": [],
- "keywords": ["cool", "hand", "like", "yes"],
+ "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": [
+ ":admission_tickets:"
+ ],
"aliases_ascii": [],
- "keywords": ["concert", "event", "pass", "show", "entertainment", "stub", "proof", "purchase"]
+ "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": [
+ ":raised_hand_with_part_between_middle_and_ring_fingers:"
+ ],
"aliases_ascii": [],
- "keywords": ["vulcan", "spock", "leonard", "nimoy", "star trek", "live long"]
+ "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"]
+ "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"
+ ]
},
"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 7efe0a0262..7d65145176 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 d293f98816..2b104f90aa 100644
--- a/lib/api/builds.rb
+++ b/lib/api/builds.rb
@@ -13,11 +13,12 @@ module API
# Example Request:
# GET /projects/:id/builds
get ':id/builds' do
+
builds = user_project.builds.order('id DESC')
builds = filter_builds(builds, params[:scope])
present paginate(builds), with: Entities::Build,
- user_can_download_artifacts: can?(current_user, :download_build_artifacts, user_project)
+ user_can_download_artifacts: can?(current_user, :read_build, user_project)
end
# Get builds for a specific commit of a project
@@ -30,6 +31,8 @@ module API
# Example Request:
# GET /projects/:id/repository/commits/:sha/builds
get ':id/repository/commits/:sha/builds' do
+ authorize_read_builds!
+
commit = user_project.ci_commits.find_by_sha(params[:sha])
return not_found! unless commit
@@ -37,7 +40,7 @@ module API
builds = filter_builds(builds, params[:scope])
present paginate(builds), with: Entities::Build,
- user_can_download_artifacts: can?(current_user, :download_build_artifacts, user_project)
+ user_can_download_artifacts: can?(current_user, :read_build, user_project)
end
# Get a specific build of a project
@@ -48,11 +51,37 @@ module API
# Example Request:
# GET /projects/:id/builds/:build_id
get ':id/builds/:build_id' do
+ authorize_read_builds!
+
build = get_build(params[:build_id])
return not_found!(build) unless build
present build, with: Entities::Build,
- user_can_download_artifacts: can?(current_user, :download_build_artifacts, user_project)
+ 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
@@ -67,6 +96,8 @@ module API
# is saved in the DB instead of file). But before that, we need to consider how to replace the value of
# `runners_token` with some mask (like `xxxxxx`) when sending trace file directly by workhorse.
get ':id/builds/:build_id/trace' do
+ authorize_read_builds!
+
build = get_build(params[:build_id])
return not_found!(build) unless build
@@ -86,7 +117,7 @@ module API
# example request:
# post /projects/:id/build/:build_id/cancel
post ':id/builds/:build_id/cancel' do
- authorize_manage_builds!
+ authorize_update_builds!
build = get_build(params[:build_id])
return not_found!(build) unless build
@@ -94,7 +125,7 @@ module API
build.cancel
present build, with: Entities::Build,
- user_can_download_artifacts: can?(current_user, :download_build_artifacts, user_project)
+ user_can_download_artifacts: can?(current_user, :read_build, user_project)
end
# Retry a specific build of a project
@@ -105,13 +136,33 @@ module API
# example request:
# post /projects/:id/build/:build_id/retry
post ':id/builds/:build_id/retry' do
- authorize_manage_builds!
+ 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
@@ -141,8 +192,12 @@ module API
builds.where(status: available_statuses && scope)
end
- def authorize_manage_builds!
- authorize! :manage_builds, user_project
+ def authorize_read_builds!
+ authorize! :read_build, user_project
+ end
+
+ def authorize_update_builds!
+ authorize! :update_build, user_project
end
end
end
diff --git a/lib/api/commit_statuses.rb b/lib/api/commit_statuses.rb
index 1162271f5f..9422d438d2 100644
--- a/lib/api/commit_statuses.rb
+++ b/lib/api/commit_statuses.rb
@@ -18,7 +18,7 @@ module API
# Examples:
# GET /projects/:id/repository/commits/:sha/statuses
get ':id/repository/commits/:sha/statuses' do
- authorize! :read_commit_statuses, user_project
+ authorize! :read_commit_status, user_project
sha = params[:sha]
ci_commit = user_project.ci_commit(sha)
not_found! 'Commit' unless ci_commit
diff --git a/lib/api/entities.rb b/lib/api/entities.rb
index 82a75734de..a3b5f1eb8d 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,11 +67,12 @@ 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? }
expose :runners_token, if: lambda { |_project, options| options[:user_can_admin_project] }
+ expose :public_builds
end
class ProjectMember < UserBasic
@@ -175,6 +176,7 @@ module API
expose :work_in_progress?, as: :work_in_progress
expose :milestone, using: Entities::Milestone
expose :merge_when_build_succeeds
+ expose :merge_status
end
class MergeRequestChanges < MergeRequest
@@ -375,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
@@ -383,9 +403,10 @@ module API
# for downloading of artifacts (see: https://gitlab.com/gitlab-org/gitlab-ce/issues/4255)
expose :download_url do |repo_obj, options|
if options[:user_can_download_artifacts]
- repo_obj.download_url
+ 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/files.rb b/lib/api/files.rb
index 8ad2c1883c..c1d86f313b 100644
--- a/lib/api/files.rb
+++ b/lib/api/files.rb
@@ -58,9 +58,11 @@ module API
commit = user_project.commit(ref)
not_found! 'Commit' unless commit
- blob = user_project.repository.blob_at(commit.sha, file_path)
+ repo = user_project.repository
+ blob = repo.blob_at(commit.sha, file_path)
if blob
+ blob.load_all_data!(repo)
status(200)
{
@@ -72,7 +74,7 @@ module API
ref: ref,
blob_id: blob.id,
commit_id: commit.id,
- last_commit_id: user_project.repository.last_commit_for_path(commit.sha, file_path).id
+ last_commit_id: repo.last_commit_for_path(commit.sha, file_path).id
}
else
not_found! 'File'
diff --git a/lib/api/helpers.rb b/lib/api/helpers.rb
index c4f58c7911..a72044e805 100644
--- a/lib/api/helpers.rb
+++ b/lib/api/helpers.rb
@@ -30,7 +30,7 @@ module API
end
def sudo_identifier()
- identifier ||= params[SUDO_PARAM] ||= env[SUDO_HEADER]
+ identifier ||= params[SUDO_PARAM] || env[SUDO_HEADER]
# Regex for integers
if !!(identifier =~ /^[0-9]+$/)
@@ -265,6 +265,10 @@ module API
projects = projects.search(params[:search])
end
+ if params[:visibility].present?
+ projects = projects.search_by_visibility(params[:visibility])
+ end
+
projects.reorder(project_order_by => project_sort)
end
@@ -340,12 +344,22 @@ module API
def pagination_links(paginated_data)
request_url = request.url.split('?').first
+ request_params = params.clone
+ request_params[:per_page] = paginated_data.limit_value
links = []
- links << %(<#{request_url}?page=#{paginated_data.current_page - 1}&per_page=#{paginated_data.limit_value}>; rel="prev") unless paginated_data.first_page?
- links << %(<#{request_url}?page=#{paginated_data.current_page + 1}&per_page=#{paginated_data.limit_value}>; rel="next") unless paginated_data.last_page?
- links << %(<#{request_url}?page=1&per_page=#{paginated_data.limit_value}>; rel="first")
- links << %(<#{request_url}?page=#{paginated_data.total_pages}&per_page=#{paginated_data.limit_value}>; rel="last")
+
+ request_params[:page] = paginated_data.current_page - 1
+ links << %(<#{request_url}?#{request_params.to_query}>; rel="prev") unless paginated_data.first_page?
+
+ request_params[:page] = paginated_data.current_page + 1
+ links << %(<#{request_url}?#{request_params.to_query}>; rel="next") unless paginated_data.last_page?
+
+ request_params[:page] = 1
+ links << %(<#{request_url}?#{request_params.to_query}>; rel="first")
+
+ request_params[:page] = paginated_data.total_pages
+ links << %(<#{request_url}?#{request_params.to_query}>; rel="last")
links.join(', ')
end
diff --git a/lib/api/issues.rb b/lib/api/issues.rb
index 6e7a767207..252744515d 100644
--- a/lib/api/issues.rb
+++ b/lib/api/issues.rb
@@ -3,6 +3,8 @@ module API
class Issues < Grape::API
before { authenticate! }
+ helpers ::Gitlab::AkismetHelper
+
helpers do
def filter_issues_state(issues, state)
case state
@@ -19,6 +21,17 @@ module API
def filter_issues_milestone(issues, milestone)
issues.includes(:milestone).where('milestones.title' => milestone)
end
+
+ def create_spam_log(project, current_user, attrs)
+ params = attrs.merge({
+ source_ip: env['REMOTE_ADDR'],
+ user_agent: env['HTTP_USER_AGENT'],
+ noteable_type: 'Issue',
+ via_api: true
+ })
+
+ ::CreateSpamLogService.new(project, current_user, params).execute
+ end
end
resource :issues do
@@ -114,7 +127,15 @@ module API
render_api_error!({ labels: errors }, 400)
end
- issue = ::Issues::CreateService.new(user_project, current_user, attrs).execute
+ project = user_project
+ text = [attrs[:title], attrs[:description]].reject(&:blank?).join("\n")
+
+ if check_for_spam?(project, current_user) && is_spam?(env, current_user, text)
+ create_spam_log(project, current_user, attrs)
+ render_api_error!({ error: 'Spam detected' }, 400)
+ end
+
+ issue = ::Issues::CreateService.new(project, current_user, attrs).execute
if issue.valid?
# Find or create labels and attach to issue. Labels are valid because
diff --git a/lib/api/merge_requests.rb b/lib/api/merge_requests.rb
index 5c97fe1c88..c5e5d57ed4 100644
--- a/lib/api/merge_requests.rb
+++ b/lib/api/merge_requests.rb
@@ -59,55 +59,6 @@ module API
present paginate(merge_requests), with: Entities::MergeRequest
end
- # Show MR
- #
- # Parameters:
- # id (required) - The ID of a project
- # merge_request_id (required) - The ID of MR
- #
- # Example:
- # GET /projects/:id/merge_request/:merge_request_id
- #
- get ":id/merge_request/:merge_request_id" do
- merge_request = user_project.merge_requests.find(params[:merge_request_id])
-
- authorize! :read_merge_request, merge_request
-
- present merge_request, with: Entities::MergeRequest
- end
-
- # Show MR commits
- #
- # Parameters:
- # id (required) - The ID of a project
- # merge_request_id (required) - The ID of MR
- #
- # Example:
- # GET /projects/:id/merge_request/:merge_request_id/commits
- #
- get ':id/merge_request/:merge_request_id/commits' do
- merge_request = user_project.merge_requests.
- find(params[:merge_request_id])
- authorize! :read_merge_request, merge_request
- present merge_request.commits, with: Entities::RepoCommit
- end
-
- # Show MR changes
- #
- # Parameters:
- # id (required) - The ID of a project
- # merge_request_id (required) - The ID of MR
- #
- # Example:
- # GET /projects/:id/merge_request/:merge_request_id/changes
- #
- get ':id/merge_request/:merge_request_id/changes' do
- merge_request = user_project.merge_requests.
- find(params[:merge_request_id])
- authorize! :read_merge_request, merge_request
- present merge_request, with: Entities::MergeRequestChanges
- end
-
# Create MR
#
# Parameters:
@@ -120,6 +71,7 @@ module API
# title (required) - Title of MR
# description - Description of MR
# labels (optional) - Labels for MR as a comma-separated list
+ # milestone_id (optional) - Milestone ID
#
# Example:
# POST /projects/:id/merge_requests
@@ -127,7 +79,7 @@ module API
post ":id/merge_requests" do
authorize! :create_merge_request, user_project
required_attributes! [:source_branch, :target_branch, :title]
- attrs = attributes_for_keys [:source_branch, :target_branch, :assignee_id, :title, :target_project_id, :description]
+ attrs = attributes_for_keys [:source_branch, :target_branch, :assignee_id, :title, :target_project_id, :description, :milestone_id]
# Validate label names in advance
if (errors = validate_label_params(params)).any?
@@ -148,146 +100,220 @@ module API
end
end
- # Update MR
+ # Routing "merge_request/:merge_request_id/..." is DEPRECATED and WILL BE REMOVED in version 9.0
+ # Use "merge_requests/:merge_request_id/..." instead.
#
- # Parameters:
- # id (required) - The ID of a project
- # merge_request_id (required) - ID of MR
- # target_branch - The target branch
- # assignee_id - Assignee user ID
- # title - Title of MR
- # state_event - Status of MR. (close|reopen|merge)
- # description - Description of MR
- # labels (optional) - Labels for a MR as a comma-separated list
- # Example:
- # PUT /projects/:id/merge_request/:merge_request_id
- #
- put ":id/merge_request/:merge_request_id" do
- attrs = attributes_for_keys [:target_branch, :assignee_id, :title, :state_event, :description]
- merge_request = user_project.merge_requests.find(params[:merge_request_id])
- authorize! :update_merge_request, merge_request
+ [":id/merge_request/:merge_request_id", ":id/merge_requests/:merge_request_id"].each do |path|
+ # Show MR
+ #
+ # Parameters:
+ # id (required) - The ID of a project
+ # merge_request_id (required) - The ID of MR
+ #
+ # Example:
+ # GET /projects/:id/merge_requests/:merge_request_id
+ #
+ get path do
+ merge_request = user_project.merge_requests.find(params[:merge_request_id])
- # Ensure source_branch is not specified
- if params[:source_branch].present?
- render_api_error!('Source branch cannot be changed', 400)
+ authorize! :read_merge_request, merge_request
+
+ present merge_request, with: Entities::MergeRequest
end
- # Validate label names in advance
- if (errors = validate_label_params(params)).any?
- render_api_error!({ labels: errors }, 400)
+ # Show MR commits
+ #
+ # Parameters:
+ # id (required) - The ID of a project
+ # merge_request_id (required) - The ID of MR
+ #
+ # Example:
+ # GET /projects/:id/merge_requests/:merge_request_id/commits
+ #
+ get "#{path}/commits" do
+ merge_request = user_project.merge_requests.
+ find(params[:merge_request_id])
+ authorize! :read_merge_request, merge_request
+ present merge_request.commits, with: Entities::RepoCommit
end
- merge_request = ::MergeRequests::UpdateService.new(user_project, current_user, attrs).execute(merge_request)
+ # Show MR changes
+ #
+ # Parameters:
+ # id (required) - The ID of a project
+ # merge_request_id (required) - The ID of MR
+ #
+ # Example:
+ # GET /projects/:id/merge_requests/:merge_request_id/changes
+ #
+ get "#{path}/changes" do
+ merge_request = user_project.merge_requests.
+ find(params[:merge_request_id])
+ authorize! :read_merge_request, merge_request
+ present merge_request, with: Entities::MergeRequestChanges
+ end
- if merge_request.valid?
- # Find or create labels and attach to issue
- unless params[:labels].nil?
- merge_request.remove_labels
- merge_request.add_labels_by_names(params[:labels].split(","))
+ # Update MR
+ #
+ # Parameters:
+ # id (required) - The ID of a project
+ # merge_request_id (required) - ID of MR
+ # target_branch - The target branch
+ # assignee_id - Assignee user ID
+ # title - Title of MR
+ # state_event - Status of MR. (close|reopen|merge)
+ # description - Description of MR
+ # labels (optional) - Labels for a MR as a comma-separated list
+ # milestone_id (optional) - Milestone ID
+ # Example:
+ # PUT /projects/:id/merge_requests/:merge_request_id
+ #
+ put path do
+ attrs = attributes_for_keys [:target_branch, :assignee_id, :title, :state_event, :description, :milestone_id]
+ merge_request = user_project.merge_requests.find(params[:merge_request_id])
+ authorize! :update_merge_request, merge_request
+
+ # Ensure source_branch is not specified
+ if params[:source_branch].present?
+ render_api_error!('Source branch cannot be changed', 400)
+ end
+
+ # Validate label names in advance
+ if (errors = validate_label_params(params)).any?
+ render_api_error!({ labels: errors }, 400)
+ end
+
+ merge_request = ::MergeRequests::UpdateService.new(user_project, current_user, attrs).execute(merge_request)
+
+ if merge_request.valid?
+ # Find or create labels and attach to issue
+ unless params[:labels].nil?
+ merge_request.remove_labels
+ merge_request.add_labels_by_names(params[:labels].split(","))
+ end
+
+ present merge_request, with: Entities::MergeRequest
+ else
+ handle_merge_request_errors! merge_request.errors
+ end
+ end
+
+ # Merge MR
+ #
+ # Parameters:
+ # id (required) - The ID of a project
+ # merge_request_id (required) - ID of MR
+ # merge_commit_message (optional) - Custom merge commit message
+ # should_remove_source_branch (optional) - When true, the source branch will be deleted if possible
+ # merge_when_build_succeeds (optional) - When true, this MR will be merged when the build succeeds
+ # Example:
+ # PUT /projects/:id/merge_requests/:merge_request_id/merge
+ #
+ put "#{path}/merge" do
+ merge_request = user_project.merge_requests.find(params[:merge_request_id])
+
+ # Merge request can not be merged
+ # because user dont have permissions to push into target branch
+ unauthorized! unless merge_request.can_be_merged_by?(current_user)
+ not_allowed! if !merge_request.open? || merge_request.work_in_progress?
+
+ merge_request.check_if_can_be_merged
+
+ render_api_error!('Branch cannot be merged', 406) unless merge_request.can_be_merged?
+
+ merge_params = {
+ commit_message: params[:merge_commit_message],
+ should_remove_source_branch: params[:should_remove_source_branch]
+ }
+
+ if parse_boolean(params[:merge_when_build_succeeds]) && merge_request.ci_commit && merge_request.ci_commit.active?
+ ::MergeRequests::MergeWhenBuildSucceedsService.new(merge_request.target_project, current_user, merge_params).
+ execute(merge_request)
+ else
+ ::MergeRequests::MergeService.new(merge_request.target_project, current_user, merge_params).
+ execute(merge_request)
end
present merge_request, with: Entities::MergeRequest
- else
- handle_merge_request_errors! merge_request.errors
- end
- end
-
- # Merge MR
- #
- # Parameters:
- # id (required) - The ID of a project
- # merge_request_id (required) - ID of MR
- # merge_commit_message (optional) - Custom merge commit message
- # should_remove_source_branch (optional) - When true, the source branch will be deleted if possible
- # merge_when_build_succeeds (optional) - When true, this MR will be merged when the build succeeds
- # Example:
- # PUT /projects/:id/merge_request/:merge_request_id/merge
- #
- put ":id/merge_request/:merge_request_id/merge" do
- merge_request = user_project.merge_requests.find(params[:merge_request_id])
-
- # Merge request can not be merged
- # because user dont have permissions to push into target branch
- unauthorized! unless merge_request.can_be_merged_by?(current_user)
- not_allowed! if !merge_request.open? || merge_request.work_in_progress?
-
- merge_request.check_if_can_be_merged
-
- render_api_error!('Branch cannot be merged', 406) unless merge_request.can_be_merged?
-
- merge_params = {
- commit_message: params[:merge_commit_message],
- should_remove_source_branch: params[:should_remove_source_branch]
- }
-
- if parse_boolean(params[:merge_when_build_succeeds]) && merge_request.ci_commit && merge_request.ci_commit.active?
- ::MergeRequests::MergeWhenBuildSucceedsService.new(merge_request.target_project, current_user, merge_params).
- execute(merge_request)
- else
- ::MergeRequests::MergeService.new(merge_request.target_project, current_user, merge_params).
- execute(merge_request)
end
- present merge_request, with: Entities::MergeRequest
- end
+ # Cancel Merge if Merge When build succeeds is enabled
+ # Parameters:
+ # id (required) - The ID of a project
+ # merge_request_id (required) - ID of MR
+ #
+ post "#{path}/cancel_merge_when_build_succeeds" do
+ merge_request = user_project.merge_requests.find(params[:merge_request_id])
- # Cancel Merge if Merge When build succeeds is enabled
- # Parameters:
- # id (required) - The ID of a project
- # merge_request_id (required) - ID of MR
- #
- post ":id/merge_request/:merge_request_id/cancel_merge_when_build_succeeds" do
- merge_request = user_project.merge_requests.find(params[:merge_request_id])
+ unauthorized! unless merge_request.can_cancel_merge_when_build_succeeds?(current_user)
- unauthorized! unless merge_request.can_cancel_merge_when_build_succeeds?(current_user)
+ ::MergeRequest::MergeWhenBuildSucceedsService.new(merge_request.target_project, current_user).cancel(merge_request)
+ end
- ::MergeRequest::MergeWhenBuildSucceedsService.new(merge_request.target_project, current_user).cancel(merge_request)
- end
+ # Duplicate. DEPRECATED and WILL BE REMOVED in 9.0.
+ # Use GET "/projects/:id/merge_requests/:merge_request_id/notes" instead
+ #
+ # Get a merge request's comments
+ #
+ # Parameters:
+ # id (required) - The ID of a project
+ # merge_request_id (required) - ID of MR
+ # Examples:
+ # GET /projects/:id/merge_requests/:merge_request_id/comments
+ #
+ get "#{path}/comments" do
+ merge_request = user_project.merge_requests.find(params[:merge_request_id])
- # Get a merge request's comments
- #
- # Parameters:
- # id (required) - The ID of a project
- # merge_request_id (required) - ID of MR
- # Examples:
- # GET /projects/:id/merge_request/:merge_request_id/comments
- #
- get ":id/merge_request/:merge_request_id/comments" do
- merge_request = user_project.merge_requests.find(params[:merge_request_id])
+ authorize! :read_merge_request, merge_request
- authorize! :read_merge_request, merge_request
+ present paginate(merge_request.notes.fresh), with: Entities::MRNote
+ end
- present paginate(merge_request.notes.fresh), with: Entities::MRNote
- end
+ # Duplicate. DEPRECATED and WILL BE REMOVED in 9.0.
+ # Use POST "/projects/:id/merge_requests/:merge_request_id/notes" instead
+ #
+ # Post comment to merge request
+ #
+ # Parameters:
+ # id (required) - The ID of a project
+ # merge_request_id (required) - ID of MR
+ # note (required) - Text of comment
+ # Examples:
+ # POST /projects/:id/merge_requests/:merge_request_id/comments
+ #
+ post "#{path}/comments" do
+ required_attributes! [:note]
- # Post comment to merge request
- #
- # Parameters:
- # id (required) - The ID of a project
- # merge_request_id (required) - ID of MR
- # note (required) - Text of comment
- # Examples:
- # POST /projects/:id/merge_request/:merge_request_id/comments
- #
- post ":id/merge_request/:merge_request_id/comments" do
- required_attributes! [:note]
+ merge_request = user_project.merge_requests.find(params[:merge_request_id])
- merge_request = user_project.merge_requests.find(params[:merge_request_id])
+ authorize! :create_note, merge_request
- authorize! :create_note, merge_request
+ opts = {
+ note: params[:note],
+ noteable_type: 'MergeRequest',
+ noteable_id: merge_request.id
+ }
- opts = {
- note: params[:note],
- noteable_type: 'MergeRequest',
- noteable_id: merge_request.id
- }
+ note = ::Notes::CreateService.new(user_project, current_user, opts).execute
- note = ::Notes::CreateService.new(user_project, current_user, opts).execute
+ if note.save
+ present note, with: Entities::MRNote
+ else
+ render_api_error!("Failed to save note #{note.errors.messages}", 400)
+ end
+ end
- if note.save
- present note, with: Entities::MRNote
- else
- render_api_error!("Failed to save note #{note.errors.messages}", 400)
+ # List issues that will close on merge
+ #
+ # Parameters:
+ # id (required) - The ID of a project
+ # merge_request_id (required) - ID of MR
+ # Examples:
+ # GET /projects/:id/merge_requests/:merge_request_id/closes_issues
+ get "#{path}/closes_issues" do
+ merge_request = user_project.merge_requests.find(params[:merge_request_id])
+ issues = ::Kaminari.paginate_array(merge_request.closes_issues(current_user))
+ present paginate(issues), with: Entities::Issue
end
end
end
diff --git a/lib/api/projects.rb b/lib/api/projects.rb
index 71bb342f84..6067c8b4a5 100644
--- a/lib/api/projects.rb
+++ b/lib/api/projects.rb
@@ -99,6 +99,7 @@ module API
# public (optional) - if true same as setting visibility_level = 20
# visibility_level (optional) - 0 by default
# import_url (optional)
+ # public_builds (optional)
# Example Request
# POST /projects
post do
@@ -115,7 +116,8 @@ module API
:namespace_id,
:public,
:visibility_level,
- :import_url]
+ :import_url,
+ :public_builds]
attrs = map_public_to_visibility_level(attrs)
@project = ::Projects::CreateService.new(current_user, attrs).execute
if @project.saved?
@@ -145,6 +147,7 @@ module API
# public (optional) - if true same as setting visibility_level = 20
# visibility_level (optional)
# import_url (optional)
+ # public_builds (optional)
# Example Request
# POST /projects/user/:user_id
post "user/:user_id" do
@@ -161,7 +164,8 @@ module API
:shared_runners_enabled,
:public,
:visibility_level,
- :import_url]
+ :import_url,
+ :public_builds]
attrs = map_public_to_visibility_level(attrs)
@project = ::Projects::CreateService.new(user, attrs).execute
if @project.saved?
@@ -187,7 +191,7 @@ module API
else
present @forked_project, with: Entities::Project,
user_can_admin_project: can?(current_user, :admin_project, @forked_project)
- end
+ end
end
# Update an existing project
@@ -205,6 +209,7 @@ module API
# shared_runners_enabled (optional)
# public (optional) - if true same as setting visibility_level = 20
# visibility_level (optional) - visibility level of a project
+ # public_builds (optional)
# Example Request
# PUT /projects/:id
put ':id' do
@@ -219,7 +224,8 @@ module API
:snippets_enabled,
:shared_runners_enabled,
:public,
- :visibility_level]
+ :visibility_level,
+ :public_builds]
attrs = map_public_to_visibility_level(attrs)
authorize_admin_project
authorize! :rename_project, user_project if attrs[:name].present?
@@ -246,7 +252,7 @@ module API
# DELETE /projects/:id
delete ":id" do
authorize! :remove_project, user_project
- ::Projects::DestroyService.new(user_project, current_user, {}).execute
+ ::Projects::DestroyService.new(user_project, current_user, {}).pending_delete!
end
# Mark this project as forked from another
diff --git a/lib/api/repositories.rb b/lib/api/repositories.rb
index d7c48639eb..c95d2d2001 100644
--- a/lib/api/repositories.rb
+++ b/lib/api/repositories.rb
@@ -57,7 +57,7 @@ module API
not_found! "File" unless blob
content_type 'text/plain'
- present blob.data
+ header *Gitlab::Workhorse.send_git_blob(repo, blob)
end
# Get a raw blob contents by blob sha
@@ -83,7 +83,7 @@ module API
env['api.format'] = :txt
content_type blob.mime_type
- present blob.data
+ header *Gitlab::Workhorse.send_git_blob(repo, blob)
end
# Get a an archive of the repository
diff --git a/lib/api/runners.rb b/lib/api/runners.rb
new file mode 100644
index 0000000000..8ec91485b2
--- /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/api/triggers.rb b/lib/api/triggers.rb
index 5e4964f446..d1d07394e9 100644
--- a/lib/api/triggers.rb
+++ b/lib/api/triggers.rb
@@ -54,7 +54,7 @@ module API
# GET /projects/:id/triggers
get ':id/triggers' do
authenticate!
- authorize_admin_project
+ authorize! :admin_build, user_project
triggers = user_project.triggers.includes(:trigger_requests)
triggers = paginate(triggers)
@@ -71,7 +71,7 @@ module API
# GET /projects/:id/triggers/:token
get ':id/triggers/:token' do
authenticate!
- authorize_admin_project
+ authorize! :admin_build, user_project
trigger = user_project.triggers.find_by(token: params[:token].to_s)
return not_found!('Trigger') unless trigger
@@ -87,7 +87,7 @@ module API
# POST /projects/:id/triggers
post ':id/triggers' do
authenticate!
- authorize_admin_project
+ authorize! :admin_build, user_project
trigger = user_project.triggers.create
@@ -103,7 +103,7 @@ module API
# DELETE /projects/:id/triggers/:token
delete ':id/triggers/:token' do
authenticate!
- authorize_admin_project
+ authorize! :admin_build, user_project
trigger = user_project.triggers.find_by(token: params[:token].to_s)
return not_found!('Trigger') unless trigger
diff --git a/lib/api/variables.rb b/lib/api/variables.rb
index d9a055f6c9..f6495071a1 100644
--- a/lib/api/variables.rb
+++ b/lib/api/variables.rb
@@ -2,7 +2,7 @@ module API
# Projects variables API
class Variables < Grape::API
before { authenticate! }
- before { authorize_admin_project }
+ before { authorize! :admin_build, user_project }
resource :projects do
# Get project variables
diff --git a/lib/backup/manager.rb b/lib/backup/manager.rb
index 099062eeb8..4962f5e53c 100644
--- a/lib/backup/manager.rb
+++ b/lib/backup/manager.rb
@@ -1,6 +1,9 @@
module Backup
class Manager
def pack
+ # Make sure there is a connection
+ ActiveRecord::Base.connection.reconnect!
+
# saving additional informations
s = {}
s[:db_version] = "#{ActiveRecord::Migrator.current_version}"
diff --git a/lib/banzai/filter/emoji_filter.rb b/lib/banzai/filter/emoji_filter.rb
index 5952a03162..207437ba7c 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/sanitization_filter.rb b/lib/banzai/filter/sanitization_filter.rb
index 3f49d492f2..04ddfe53ed 100644
--- a/lib/banzai/filter/sanitization_filter.rb
+++ b/lib/banzai/filter/sanitization_filter.rb
@@ -8,14 +8,7 @@ module Banzai
# Extends HTML::Pipeline::SanitizationFilter with a custom whitelist.
class SanitizationFilter < HTML::Pipeline::SanitizationFilter
def whitelist
- # Descriptions are more heavily sanitized, allowing only a few elements.
- # See http://git.io/vkuAN
- if context[:inline_sanitization]
- whitelist = LIMITED
- whitelist[:elements] -= %w(pre code img ol ul li)
- else
- whitelist = super
- end
+ whitelist = super
customize_whitelist(whitelist)
@@ -43,6 +36,10 @@ module Banzai
# Allow span elements
whitelist[:elements].push('span')
+ # Allow abbr elements with title attribute
+ whitelist[:elements].push('abbr')
+ whitelist[:attributes]['abbr'] = %w(title)
+
# Allow any protocol in `a` elements...
whitelist[:protocols].delete('a')
diff --git a/lib/banzai/pipeline/asciidoc_pipeline.rb b/lib/banzai/pipeline/asciidoc_pipeline.rb
deleted file mode 100644
index f1331c0ebf..0000000000
--- a/lib/banzai/pipeline/asciidoc_pipeline.rb
+++ /dev/null
@@ -1,11 +0,0 @@
-module Banzai
- module Pipeline
- class AsciidocPipeline < BasePipeline
- def self.filters
- [
- Filter::RelativeLinkFilter
- ]
- end
- end
- end
-end
diff --git a/lib/banzai/pipeline/broadcast_message_pipeline.rb b/lib/banzai/pipeline/broadcast_message_pipeline.rb
new file mode 100644
index 0000000000..4bb85e24c3
--- /dev/null
+++ b/lib/banzai/pipeline/broadcast_message_pipeline.rb
@@ -0,0 +1,16 @@
+module Banzai
+ module Pipeline
+ class BroadcastMessagePipeline < DescriptionPipeline
+ def self.filters
+ @filters ||= [
+ Filter::MarkdownFilter,
+ Filter::SanitizationFilter,
+
+ Filter::EmojiFilter,
+ Filter::AutolinkFilter,
+ Filter::ExternalLinkFilter
+ ]
+ end
+ end
+ end
+end
diff --git a/lib/banzai/pipeline/description_pipeline.rb b/lib/banzai/pipeline/description_pipeline.rb
index 20e24ace35..f239586765 100644
--- a/lib/banzai/pipeline/description_pipeline.rb
+++ b/lib/banzai/pipeline/description_pipeline.rb
@@ -4,9 +4,20 @@ module Banzai
def self.transform_context(context)
super(context).merge(
# SanitizationFilter
- inline_sanitization: true
+ whitelist: whitelist
)
end
+
+ private
+
+ def self.whitelist
+ # Descriptions are more heavily sanitized, allowing only a few elements.
+ # See http://git.io/vkuAN
+ whitelist = Banzai::Filter::SanitizationFilter::LIMITED
+ whitelist[:elements] -= %w(pre code img ol ul li)
+
+ whitelist
+ end
end
end
end
diff --git a/lib/ci/api/api.rb b/lib/ci/api/api.rb
index 5c347e432b..4e85d2c3c7 100644
--- a/lib/ci/api/api.rb
+++ b/lib/ci/api/api.rb
@@ -25,7 +25,7 @@ module Ci
format :json
- helpers Helpers
+ helpers ::Ci::API::Helpers
helpers ::API::Helpers
helpers Gitlab::CurrentSettings
diff --git a/lib/ci/api/builds.rb b/lib/ci/api/builds.rb
index 416b0b5f0b..2e9a5d311f 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 c02b3b8f3e..3fb1fe2949 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/dnsxl_check.rb b/lib/dnsxl_check.rb
deleted file mode 100644
index 1e506b2d9c..0000000000
--- a/lib/dnsxl_check.rb
+++ /dev/null
@@ -1,105 +0,0 @@
-require 'resolv'
-
-class DNSXLCheck
-
- class Resolver
- def self.search(query)
- begin
- Resolv.getaddress(query)
- true
- rescue Resolv::ResolvError
- false
- end
- end
- end
-
- IP_REGEXP = /\A(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\z/
- DEFAULT_THRESHOLD = 0.33
-
- def self.create_from_list(list)
- dnsxl_check = DNSXLCheck.new
-
- list.each do |entry|
- dnsxl_check.add_list(entry.domain, entry.weight)
- end
-
- dnsxl_check
- end
-
- def test(ip)
- if use_threshold?
- test_with_threshold(ip)
- else
- test_strict(ip)
- end
- end
-
- def test_with_threshold(ip)
- return false if lists.empty?
-
- search(ip)
- final_score >= threshold
- end
-
- def test_strict(ip)
- return false if lists.empty?
-
- search(ip)
- @score > 0
- end
-
- def use_threshold=(value)
- @use_threshold = value == true
- end
-
- def use_threshold?
- @use_threshold &&= true
- end
-
- def threshold=(threshold)
- raise ArgumentError, "'threshold' value must be grather than 0 and less than or equal to 1" unless threshold > 0 && threshold <= 1
- @threshold = threshold
- end
-
- def threshold
- @threshold ||= DEFAULT_THRESHOLD
- end
-
- def add_list(domain, weight)
- @lists ||= []
- @lists << { domain: domain, weight: weight }
- end
-
- def lists
- @lists ||= []
- end
-
- private
-
- def search(ip)
- raise ArgumentError, "'ip' value must be in #{IP_REGEXP} format" unless ip.match(IP_REGEXP)
-
- @score = 0
-
- reversed = reverse_ip(ip)
- search_in_rbls(reversed)
- end
-
- def reverse_ip(ip)
- ip.split('.').reverse.join('.')
- end
-
- def search_in_rbls(reversed_ip)
- lists.each do |rbl|
- query = "#{reversed_ip}.#{rbl[:domain]}"
- @score += rbl[:weight] if Resolver.search(query)
- end
- end
-
- def final_score
- weights = lists.map{ |rbl| rbl[:weight] }.reduce(:+).to_i
- return 0 if weights == 0
-
- (@score.to_f / weights.to_f).round(2)
- end
-end
diff --git a/lib/gitlab/akismet_helper.rb b/lib/gitlab/akismet_helper.rb
new file mode 100644
index 0000000000..b366c89889
--- /dev/null
+++ b/lib/gitlab/akismet_helper.rb
@@ -0,0 +1,39 @@
+module Gitlab
+ module AkismetHelper
+ def akismet_enabled?
+ current_application_settings.akismet_enabled
+ end
+
+ def akismet_client
+ @akismet_client ||= ::Akismet::Client.new(current_application_settings.akismet_api_key,
+ Gitlab.config.gitlab.url)
+ end
+
+ def check_for_spam?(project, user)
+ akismet_enabled? && !project.team.member?(user)
+ end
+
+ def is_spam?(environment, user, text)
+ client = akismet_client
+ ip_address = environment['REMOTE_ADDR']
+ user_agent = environment['HTTP_USER_AGENT']
+
+ params = {
+ type: 'comment',
+ text: text,
+ created_at: DateTime.now,
+ author: user.name,
+ author_email: user.email,
+ referrer: environment['HTTP_REFERER'],
+ }
+
+ begin
+ is_spam, is_blatant = client.check(ip_address, user_agent, params)
+ is_spam || is_blatant
+ rescue => e
+ Rails.logger.error("Unable to connect to Akismet: #{e}, skipping check")
+ false
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/asciidoc.rb b/lib/gitlab/asciidoc.rb
index b203b9d70e..0b9c2e730f 100644
--- a/lib/gitlab/asciidoc.rb
+++ b/lib/gitlab/asciidoc.rb
@@ -31,9 +31,7 @@ module Gitlab
html = ::Asciidoctor.convert(input, asciidoc_opts)
- if context[:project]
- html = Banzai.render(html, context.merge(pipeline: :asciidoc))
- end
+ html = Banzai.post_process(html, context)
html.html_safe
end
diff --git a/lib/gitlab/backend/shell.rb b/lib/gitlab/backend/shell.rb
index 4c15d58d68..b9bb6e7608 100644
--- a/lib/gitlab/backend/shell.rb
+++ b/lib/gitlab/backend/shell.rb
@@ -36,7 +36,7 @@ module Gitlab
# import_repository("gitlab/gitlab-ci", "https://github.com/randx/six.git")
#
def import_repository(name, url)
- output, status = Popen::popen([gitlab_shell_projects_path, 'import-project', "#{name}.git", url, '240'])
+ output, status = Popen::popen([gitlab_shell_projects_path, 'import-project', "#{name}.git", url, '900'])
raise Error, output unless status.zero?
true
end
@@ -47,7 +47,7 @@ module Gitlab
# new_path - new project path with namespace
#
# Ex.
- # mv_repository("gitlab/gitlab-ci", "randx/gitlab-ci-new.git")
+ # mv_repository("gitlab/gitlab-ci", "randx/gitlab-ci-new")
#
def mv_repository(path, new_path)
Gitlab::Utils.system_silent([gitlab_shell_projects_path, 'mv-project',
diff --git a/lib/gitlab/bitbucket_import/importer.rb b/lib/gitlab/bitbucket_import/importer.rb
index 2355b3c6dd..3f483847ef 100644
--- a/lib/gitlab/bitbucket_import/importer.rb
+++ b/lib/gitlab/bitbucket_import/importer.rb
@@ -13,12 +13,36 @@ module Gitlab
end
def execute
- project_identifier = project.import_source
+ import_issues if has_issues?
- return true unless client.project(project_identifier)["has_issues"]
+ true
+ rescue ActiveRecord::RecordInvalid => e
+ raise Projects::ImportService::Error.new, e.message
+ ensure
+ Gitlab::BitbucketImport::KeyDeleter.new(project).execute
+ end
- #Issues && Comments
- issues = client.issues(project_identifier)
+ private
+
+ def gl_user_id(project, bitbucket_id)
+ if bitbucket_id
+ user = User.joins(:identities).find_by("identities.extern_uid = ? AND identities.provider = 'bitbucket'", bitbucket_id.to_s)
+ (user && user.id) || project.creator_id
+ else
+ project.creator_id
+ end
+ end
+
+ def identifier
+ project.import_source
+ end
+
+ def has_issues?
+ client.project(identifier)["has_issues"]
+ end
+
+ def import_issues
+ issues = client.issues(identifier)
issues.each do |issue|
body = ''
@@ -33,7 +57,7 @@ module Gitlab
body = @formatter.author_line(author)
body += issue["content"]
- comments = client.issue_comments(project_identifier, issue["local_id"])
+ comments = client.issue_comments(identifier, issue["local_id"])
if comments.any?
body += @formatter.comments_header
@@ -56,20 +80,9 @@ module Gitlab
author_id: gl_user_id(project, reporter)
)
end
-
- true
+ rescue ActiveRecord::RecordInvalid => e
+ raise Projects::ImportService::Error, e.message
end
-
- private
-
- def gl_user_id(project, bitbucket_id)
- if bitbucket_id
- user = User.joins(:identities).find_by("identities.extern_uid = ? AND identities.provider = 'bitbucket'", bitbucket_id.to_s)
- (user && user.id) || project.creator_id
- else
- project.creator_id
- end
- end
end
end
end
diff --git a/lib/gitlab/blame.rb b/lib/gitlab/blame.rb
index 313e6b9fc0..997a22779a 100644
--- a/lib/gitlab/blame.rb
+++ b/lib/gitlab/blame.rb
@@ -40,6 +40,7 @@ module Gitlab
end
def highlighted_lines
+ @blob.load_all_data!(repository)
@highlighted_lines ||= Gitlab::Highlight.highlight(@blob.name, @blob.data).lines
end
diff --git a/lib/gitlab/current_settings.rb b/lib/gitlab/current_settings.rb
index 7f938780ab..761b63e98f 100644
--- a/lib/gitlab/current_settings.rb
+++ b/lib/gitlab/current_settings.rb
@@ -4,11 +4,14 @@ module Gitlab
key = :current_application_settings
RequestStore.store[key] ||= begin
+ settings = nil
+
if connect_to_db?
- ApplicationSetting.current || ApplicationSetting.create_from_defaults
- else
- fake_application_settings
+ settings = ::ApplicationSetting.current
+ settings ||= ::ApplicationSetting.create_from_defaults unless ActiveRecord::Migrator.needs_migration?
end
+
+ settings || fake_application_settings
end
end
@@ -18,29 +21,33 @@ module Gitlab
default_branch_protection: Settings.gitlab['default_branch_protection'],
signup_enabled: Settings.gitlab['signup_enabled'],
signin_enabled: Settings.gitlab['signin_enabled'],
+ twitter_sharing_enabled: Settings.gitlab['twitter_sharing_enabled'],
gravatar_enabled: Settings.gravatar['enabled'],
sign_in_text: Settings.extra['sign_in_text'],
restricted_visibility_levels: Settings.gitlab['restricted_visibility_levels'],
max_attachment_size: Settings.gitlab['max_attachment_size'],
session_expire_delay: Settings.gitlab['session_expire_delay'],
- import_sources: Settings.gitlab['import_sources'],
+ default_project_visibility: Settings.gitlab.default_projects_features['visibility_level'],
+ default_snippet_visibility: Settings.gitlab.default_projects_features['visibility_level'],
+ restricted_signup_domains: Settings.gitlab['restricted_signup_domains'],
+ import_sources: ['github','bitbucket','gitlab','gitorious','google_code','fogbugz','git'],
shared_runners_enabled: Settings.gitlab_ci['shared_runners_enabled'],
max_artifacts_size: Settings.artifacts['max_size'],
+ require_two_factor_authentication: false,
+ two_factor_grace_period: 48,
+ akismet_enabled: false
)
end
private
def connect_to_db?
- use_db = if ENV['USE_DB'] == "false"
- false
- else
- true
- end
+ # When the DBMS is not available, an exception (e.g. PG::ConnectionBad) is raised
+ active_db_connection = ActiveRecord::Base.connection.active? rescue false
- use_db && ActiveRecord::Base.connection.active? &&
- !ActiveRecord::Migrator.needs_migration? &&
- ActiveRecord::Base.connection.table_exists?('application_settings')
+ ENV['USE_DB'] != 'false' &&
+ active_db_connection &&
+ ActiveRecord::Base.connection.table_exists?('application_settings')
rescue ActiveRecord::NoDatabaseError
false
diff --git a/lib/gitlab/database.rb b/lib/gitlab/database.rb
index de77a6fbff..6f9da69983 100644
--- a/lib/gitlab/database.rb
+++ b/lib/gitlab/database.rb
@@ -1,16 +1,23 @@
module Gitlab
module Database
+ def self.adapter_name
+ connection.adapter_name
+ end
+
def self.mysql?
- ActiveRecord::Base.connection.adapter_name.downcase == 'mysql2'
+ adapter_name.downcase == 'mysql2'
end
def self.postgresql?
- ActiveRecord::Base.connection.adapter_name.downcase == 'postgresql'
+ adapter_name.downcase == 'postgresql'
+ end
+
+ def self.version
+ database_version.match(/\A(?:PostgreSQL |)([^\s]+).*\z/)[1]
end
def true_value
- case ActiveRecord::Base.connection.adapter_name.downcase
- when 'postgresql'
+ if Gitlab::Database.postgresql?
"'t'"
else
1
@@ -18,12 +25,27 @@ module Gitlab
end
def false_value
- case ActiveRecord::Base.connection.adapter_name.downcase
- when 'postgresql'
+ if Gitlab::Database.postgresql?
"'f'"
else
0
end
end
+
+ private
+
+ def self.connection
+ ActiveRecord::Base.connection
+ end
+
+ def self.database_version
+ row = connection.execute("SELECT VERSION()").first
+
+ if postgresql?
+ row['version']
+ else
+ row.first
+ end
+ end
end
end
diff --git a/lib/gitlab/diff/highlight.rb b/lib/gitlab/diff/highlight.rb
index a7f925ce13..9429b3ff88 100644
--- a/lib/gitlab/diff/highlight.rb
+++ b/lib/gitlab/diff/highlight.rb
@@ -21,13 +21,13 @@ module Gitlab
# ignore highlighting for "match" lines
next diff_line if diff_line.type == 'match' || diff_line.type == 'nonewline'
- rich_line = highlight_line(diff_line, i)
+ rich_line = highlight_line(diff_line) || diff_line.text
if line_inline_diffs = inline_diffs[i]
rich_line = InlineDiffMarker.new(diff_line.text, rich_line).mark(line_inline_diffs)
end
- diff_line.text = rich_line.html_safe
+ diff_line.text = rich_line
diff_line
end
@@ -35,8 +35,8 @@ module Gitlab
private
- def highlight_line(diff_line, index)
- return html_escape(diff_line.text) unless diff_file && diff_file.diff_refs
+ def highlight_line(diff_line)
+ return unless diff_file && diff_file.diff_refs
line_prefix = diff_line.text.match(/\A(.)/) ? $1 : ' '
@@ -49,11 +49,11 @@ module Gitlab
# Only update text if line is found. This will prevent
# issues with submodules given the line only exists in diff content.
- rich_line ? line_prefix + rich_line : html_escape(diff_line.text)
+ "#{line_prefix}#{rich_line}".html_safe if rich_line
end
def inline_diffs
- @inline_diffs ||= InlineDiff.new(@raw_lines).inline_diffs
+ @inline_diffs ||= InlineDiff.for_lines(@raw_lines)
end
def old_lines
@@ -72,11 +72,6 @@ module Gitlab
[ref.project.repository, ref.id, path]
end
-
- def html_escape(str)
- replacements = { '&' => '&', '>' => '>', '<' => '<', '"' => '"', "'" => ''' }
- str.gsub(/[&"'><]/, replacements)
- end
end
end
end
diff --git a/lib/gitlab/diff/inline_diff.rb b/lib/gitlab/diff/inline_diff.rb
index b8a61ad611..789c14518b 100644
--- a/lib/gitlab/diff/inline_diff.rb
+++ b/lib/gitlab/diff/inline_diff.rb
@@ -1,43 +1,58 @@
module Gitlab
module Diff
class InlineDiff
- attr_accessor :lines
+ attr_accessor :old_line, :new_line, :offset
- def initialize(lines)
- @lines = lines
- end
+ def self.for_lines(lines)
+ local_edit_indexes = self.find_local_edits(lines)
- def inline_diffs
inline_diffs = []
local_edit_indexes.each do |index|
old_index = index
new_index = index + 1
- old_line = @lines[old_index]
- new_line = @lines[new_index]
+ old_line = lines[old_index]
+ new_line = lines[new_index]
- # Skip inline diff if empty line was replaced with content
- next if old_line[1..-1] == ""
+ old_diffs, new_diffs = new(old_line, new_line, offset: 1).inline_diffs
- # Add one, because this is based on the prefixless version
- lcp = longest_common_prefix(old_line[1..-1], new_line[1..-1]) + 1
- lcs = longest_common_suffix(old_line[lcp..-1], new_line[lcp..-1])
-
- old_diff_range = lcp..(old_line.length - lcs - 1)
- new_diff_range = lcp..(new_line.length - lcs - 1)
-
- inline_diffs[old_index] = [old_diff_range] if old_diff_range.begin <= old_diff_range.end
- inline_diffs[new_index] = [new_diff_range] if new_diff_range.begin <= new_diff_range.end
+ inline_diffs[old_index] = old_diffs
+ inline_diffs[new_index] = new_diffs
end
inline_diffs
end
+ def initialize(old_line, new_line, offset: 0)
+ @old_line = old_line[offset..-1]
+ @new_line = new_line[offset..-1]
+ @offset = offset
+ end
+
+ def inline_diffs
+ # Skip inline diff if empty line was replaced with content
+ return if old_line == ""
+
+ lcp = longest_common_prefix(old_line, new_line)
+ lcs = longest_common_suffix(old_line[lcp..-1], new_line[lcp..-1])
+
+ lcp += offset
+ old_length = old_line.length + offset
+ new_length = new_line.length + offset
+
+ old_diff_range = lcp..(old_length - lcs - 1)
+ new_diff_range = lcp..(new_length - lcs - 1)
+
+ old_diffs = [old_diff_range] if old_diff_range.begin <= old_diff_range.end
+ new_diffs = [new_diff_range] if new_diff_range.begin <= new_diff_range.end
+
+ [old_diffs, new_diffs]
+ end
+
private
- # Find runs of single line edits
- def local_edit_indexes
- line_prefixes = @lines.map { |line| line.match(/\A([+-])/) ? $1 : ' ' }
+ def self.find_local_edits(lines)
+ line_prefixes = lines.map { |line| line.match(/\A([+-])/) ? $1 : ' ' }
joined_line_prefixes = " #{line_prefixes.join} "
offset = 0
diff --git a/lib/gitlab/diff/inline_diff_marker.rb b/lib/gitlab/diff/inline_diff_marker.rb
index 1d7fa1bce0..dccb717e95 100644
--- a/lib/gitlab/diff/inline_diff_marker.rb
+++ b/lib/gitlab/diff/inline_diff_marker.rb
@@ -5,10 +5,12 @@ module Gitlab
def initialize(raw_line, rich_line = raw_line)
@raw_line = raw_line
- @rich_line = rich_line
+ @rich_line = ERB::Util.html_escape(rich_line)
end
def mark(line_inline_diffs)
+ return rich_line unless line_inline_diffs
+
marker_ranges = []
line_inline_diffs.each do |inline_diff_range|
# Map the inline-diff range based on the raw line to character positions in the rich line
@@ -19,11 +21,15 @@ module Gitlab
offset = 0
# Mark each range
- marker_ranges.each do |range|
- offset = insert_around_range(rich_line, range, "", " ", offset)
+ marker_ranges.each_with_index do |range, i|
+ class_names = ["idiff"]
+ class_names << "left" if i == 0
+ class_names << "right" if i == marker_ranges.length - 1
+
+ offset = insert_around_range(rich_line, range, "", " ", offset)
end
- rich_line
+ rich_line.html_safe
end
private
diff --git a/lib/gitlab/exclusive_lease.rb b/lib/gitlab/exclusive_lease.rb
new file mode 100644
index 0000000000..abf5fbe7ff
--- /dev/null
+++ b/lib/gitlab/exclusive_lease.rb
@@ -0,0 +1,41 @@
+module Gitlab
+ # This class implements an 'exclusive lease'. We call it a 'lease'
+ # because it has a set expiry time. We call it 'exclusive' because only
+ # one caller may obtain a lease for a given key at a time. The
+ # implementation is intended to work across GitLab processes and across
+ # servers. It is a 'cheap' alternative to using SQL queries and updates:
+ # you do not need to change the SQL schema to start using
+ # ExclusiveLease.
+ #
+ # It is important to choose the timeout wisely. If the timeout is very
+ # high (1 hour) then the throughput of your operation gets very low (at
+ # most once an hour). If the timeout is lower than how long your
+ # operation may take then you cannot count on exclusivity. For example,
+ # if the timeout is 10 seconds and you do an operation which may take 20
+ # seconds then two overlapping operations may hold a lease for the same
+ # key at the same time.
+ #
+ class ExclusiveLease
+ def initialize(key, timeout:)
+ @key, @timeout = key, timeout
+ end
+
+ # Try to obtain the lease. Return true on success,
+ # false if the lease is already taken.
+ def try_obtain
+ # Performing a single SET is atomic
+ !!redis.set(redis_key, '1', nx: true, ex: @timeout)
+ end
+
+ private
+
+ def redis
+ # Maybe someday we want to use a connection pool...
+ @redis ||= Redis.new(url: Gitlab::REDIS_URL)
+ end
+
+ def redis_key
+ "gitlab:exclusive_lease:#{@key}"
+ end
+ end
+end
diff --git a/lib/gitlab/git.rb b/lib/gitlab/git.rb
index f065cc5e9e..191bea86ac 100644
--- a/lib/gitlab/git.rb
+++ b/lib/gitlab/git.rb
@@ -1,8 +1,8 @@
module Gitlab
module Git
- BLANK_SHA = '0' * 40
- TAG_REF_PREFIX = "refs/tags/"
- BRANCH_REF_PREFIX = "refs/heads/"
+ BLANK_SHA = ('0' * 40).freeze
+ TAG_REF_PREFIX = "refs/tags/".freeze
+ BRANCH_REF_PREFIX = "refs/heads/".freeze
class << self
def ref_name(ref)
diff --git a/lib/gitlab/github_import/importer.rb b/lib/gitlab/github_import/importer.rb
index 663402e819..e2a85f2982 100644
--- a/lib/gitlab/github_import/importer.rb
+++ b/lib/gitlab/github_import/importer.rb
@@ -35,8 +35,8 @@ module Gitlab
end
true
- rescue ActiveRecord::RecordInvalid
- false
+ rescue ActiveRecord::RecordInvalid => e
+ raise Projects::ImportService::Error, e.message
end
def import_pull_requests
@@ -53,8 +53,8 @@ module Gitlab
end
true
- rescue ActiveRecord::RecordInvalid
- false
+ rescue ActiveRecord::RecordInvalid => e
+ raise Projects::ImportService::Error, e.message
end
def import_comments(issue_number, noteable)
@@ -83,10 +83,13 @@ module Gitlab
true
rescue Gitlab::Shell::Error => e
- if e.message =~ /repository not exported/
- true
+ # GitHub error message when the wiki repo has not been created,
+ # this means that repo has wiki enabled, but have no pages. So,
+ # we can skip the import.
+ if e.message !~ /repository not exported/
+ raise Projects::ImportService::Error, e.message
else
- false
+ true
end
end
end
diff --git a/lib/gitlab/gitlab_import/importer.rb b/lib/gitlab/gitlab_import/importer.rb
index 59926084d0..850b73244c 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/highlight.rb b/lib/gitlab/highlight.rb
index 4ddb4fea97..cac7644232 100644
--- a/lib/gitlab/highlight.rb
+++ b/lib/gitlab/highlight.rb
@@ -8,6 +8,7 @@ module Gitlab
blob = repository.blob_at(ref, file_name)
return [] unless blob
+ blob.load_all_data!(repository)
highlight(file_name, blob.data).lines.map!(&:html_safe)
end
diff --git a/lib/gitlab/ip_check.rb b/lib/gitlab/ip_check.rb
deleted file mode 100644
index f2e9b50d22..0000000000
--- a/lib/gitlab/ip_check.rb
+++ /dev/null
@@ -1,34 +0,0 @@
-module Gitlab
- class IpCheck
-
- def initialize(ip)
- @ip = ip
-
- application_settings = ApplicationSetting.current
- @ip_blocking_enabled = application_settings.ip_blocking_enabled
- @dnsbl_servers_list = application_settings.dnsbl_servers_list
- end
-
- def spam?
- @ip_blocking_enabled && blacklisted?
- end
-
- private
-
- def blacklisted?
- on_dns_blacklist?
- end
-
- def on_dns_blacklist?
- dnsbl_check = DNSXLCheck.new
- prepare_dnsbl_list(dnsbl_check)
- dnsbl_check.test(@ip)
- end
-
- def prepare_dnsbl_list(dnsbl_check)
- @dnsbl_servers_list.split(',').map(&:strip).reject(&:empty?).each do |domain|
- dnsbl_check.add_list(domain, 1)
- end
- end
- end
-end
diff --git a/lib/gitlab/ldap/user.rb b/lib/gitlab/ldap/user.rb
index e044f0ecc6..b84c81f1a6 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 ea6b0ee796..71cf6a0d88 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 d87a72f7ba..832fb08a52 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/other_markup.rb b/lib/gitlab/other_markup.rb
new file mode 100644
index 0000000000..746ec28333
--- /dev/null
+++ b/lib/gitlab/other_markup.rb
@@ -0,0 +1,24 @@
+module Gitlab
+ # Parser/renderer for markups without other special support code.
+ module OtherMarkup
+
+ # Public: Converts the provided markup into HTML.
+ #
+ # input - the source text in a markup format
+ # context - a Hash with the template context:
+ # :commit
+ # :project
+ # :project_wiki
+ # :requested_path
+ # :ref
+ #
+ def self.render(file_name, input, context)
+ html = GitHub::Markup.render(file_name, input).
+ force_encoding(input.encoding)
+
+ html = Banzai.post_process(html, context)
+
+ html.html_safe
+ end
+ end
+end
diff --git a/lib/gitlab/push_data_builder.rb b/lib/gitlab/push_data_builder.rb
index 4f9cdef386..da1c15fef6 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/regex.rb b/lib/gitlab/regex.rb
index 53ab2686b4..ace906a6f5 100644
--- a/lib/gitlab/regex.rb
+++ b/lib/gitlab/regex.rb
@@ -34,29 +34,29 @@ module Gitlab
def project_path_regex
- @project_path_regex ||= /\A[a-zA-Z0-9_.][a-zA-Z0-9_\-\.]*(? min ? line - surrounding_lines : min
- upper = line + surrounding_lines < max ? line + surrounding_lines : max
- (lower..upper).to_a
- end
-
- # Returns a sorted set of lines to be included in a snippet preview.
- # This ensures matching adjacent lines do not display duplicated
- # surrounding code.
- #
- # @returns Array, unique and sorted.
- def matching_lines(lined_content)
- used_lines = []
- lined_content.each_with_index do |line, line_number|
- used_lines.concat bounded_line_numbers(
- line_number,
- 0,
- lined_content.size
- ) if line.include?(query)
- end
-
- used_lines.uniq.sort
- end
-
- # 'Chunkify' entire snippet. Splits the snippet data into matching lines +
- # surrounding_lines() worth of unmatching lines.
- #
- # @returns a hash with {snippet_object, snippet_chunks:{data,start_line}}
- def chunk_snippet(snippet)
- lined_content = snippet.content.split("\n")
- used_lines = matching_lines(lined_content)
-
- snippet_chunk = []
- snippet_chunks = []
- snippet_start_line = 0
- last_line = -1
-
- # Go through each used line, and add consecutive lines as a single chunk
- # to the snippet chunk array.
- used_lines.each do |line_number|
- if last_line < 0
- # Start a new chunk.
- snippet_start_line = line_number
- snippet_chunk << lined_content[line_number]
- elsif last_line == line_number - 1
- # Consecutive line, continue chunk.
- snippet_chunk << lined_content[line_number]
- else
- # Non-consecutive line, add chunk to chunk array.
- snippet_chunks << {
- data: snippet_chunk.join("\n"),
- start_line: snippet_start_line + 1
- }
-
- # Start a new chunk.
- snippet_chunk = [lined_content[line_number]]
- snippet_start_line = line_number
- end
- last_line = line_number
- end
- # Add final chunk to chunk array
- snippet_chunks << {
- data: snippet_chunk.join("\n"),
- start_line: snippet_start_line + 1
- }
-
- # Return snippet with chunk array
- { snippet_object: snippet, snippet_chunks: snippet_chunks }
- end
-
- # Defines how many unmatching lines should be
- # included around the matching lines in a snippet
- def surrounding_lines
- 3
- end
end
end
diff --git a/lib/gitlab/user_access.rb b/lib/gitlab/user_access.rb
index 4885baf952..d1b42c1f9b 100644
--- a/lib/gitlab/user_access.rb
+++ b/lib/gitlab/user_access.rb
@@ -3,7 +3,7 @@ module Gitlab
def self.allowed?(user)
return false if user.blocked?
- if user.requires_ldap_check?
+ if user.requires_ldap_check? && user.try_obtain_ldap_lease
return false unless Gitlab::LDAP::Access.allowed?(user)
end
diff --git a/lib/gitlab/workhorse.rb b/lib/gitlab/workhorse.rb
new file mode 100644
index 0000000000..a23120a417
--- /dev/null
+++ b/lib/gitlab/workhorse.rb
@@ -0,0 +1,21 @@
+require 'base64'
+require 'json'
+
+module Gitlab
+ class Workhorse
+ class << self
+ def send_git_blob(repository, blob)
+ params_hash = {
+ 'RepoPath' => repository.path_to_repo,
+ 'BlobId' => blob.id,
+ }
+ params = Base64.urlsafe_encode64(JSON.dump(params_hash))
+
+ [
+ 'Gitlab-Workhorse-Send-Data',
+ "git-blob:#{params}",
+ ]
+ end
+ end
+ end
+end
diff --git a/lib/support/init.d/gitlab b/lib/support/init.d/gitlab
index 1633891c8a..d95e7023d2 100755
--- a/lib/support/init.d/gitlab
+++ b/lib/support/init.d/gitlab
@@ -38,7 +38,7 @@ web_server_pid_path="$pid_path/unicorn.pid"
sidekiq_pid_path="$pid_path/sidekiq.pid"
mail_room_enabled=false
mail_room_pid_path="$pid_path/mail_room.pid"
-gitlab_workhorse_dir=$(cd $app_root/../gitlab-workhorse && pwd)
+gitlab_workhorse_dir=$(cd $app_root/../gitlab-workhorse 2> /dev/null && pwd)
gitlab_workhorse_pid_path="$pid_path/gitlab-workhorse.pid"
gitlab_workhorse_options="-listenUmask 0 -listenNetwork unix -listenAddr $socket_path/gitlab-workhorse.socket -authBackend http://127.0.0.1:8080 -authSocket $rails_socket -documentRoot $app_root/public"
gitlab_workhorse_log="$app_root/log/gitlab-workhorse.log"
@@ -49,7 +49,7 @@ test -f /etc/default/gitlab && . /etc/default/gitlab
# Switch to the app_user if it is not he/she who is running the script.
if [ `whoami` != "$app_user" ]; then
- eval su - "$app_user" -s $shell_path -c $(echo \")$0 "$@"$(echo \"); exit;
+ eval su - "$app_user" -c $(echo \")$shell_path -l -c \'$0 "$@"\'$(echo \"); exit;
fi
# Switch to the gitlab path, exit on failure.
@@ -219,7 +219,7 @@ start_gitlab() {
echo "The Unicorn web server already running with pid $wpid, not restarting."
else
# Remove old socket if it exists
- rm -f "$socket_path"/gitlab.socket 2>/dev/null
+ rm -f "$rails_socket" 2>/dev/null
# Start the web server
RAILS_ENV=$RAILS_ENV bin/web start
fi
diff --git a/lib/support/init.d/gitlab.default.example b/lib/support/init.d/gitlab.default.example
index 4e6e56ac2d..cc8617b72c 100755
--- a/lib/support/init.d/gitlab.default.example
+++ b/lib/support/init.d/gitlab.default.example
@@ -34,11 +34,16 @@ sidekiq_pid_path="$pid_path/sidekiq.pid"
# /home/git/gitlab-workhorse .
gitlab_workhorse_dir=$(cd $app_root/../gitlab-workhorse && pwd)
gitlab_workhorse_pid_path="$pid_path/gitlab-workhorse.pid"
+
# The -listenXxx settings determine where gitlab-workhorse
-# listens for connections from NGINX. To listen on localhost:8181, write
-# '-listenNetwork tcp -listenAddr localhost:8181'.
-# The -authBackend setting tells gitlab-workhorse where it can reach
-# Unicorn.
+# listens for connections from the web server. By default it listens to a
+# socket. To listen on TCP connections (needed by Apache) change to:
+# '-listenNetwork tcp -listenAddr 127.0.0.1:8181'
+#
+# The -authBackend setting tells gitlab-workhorse where it can reach Unicorn.
+# For relative URL support change to:
+# '-authBackend http://127.0.0.1/8080/gitlab'
+# Read more in http://doc.gitlab.com/ce/install/relative_url.html
gitlab_workhorse_options="-listenUmask 0 -listenNetwork unix -listenAddr $socket_path/gitlab-workhorse.socket -authBackend http://127.0.0.1:8080 -authSocket $socket_path/gitlab.socket -documentRoot $app_root/public"
gitlab_workhorse_log="$app_root/log/gitlab-workhorse.log"
diff --git a/lib/tasks/cache.rake b/lib/tasks/cache.rake
index 1728dda72c..b262ea898d 100644
--- a/lib/tasks/cache.rake
+++ b/lib/tasks/cache.rake
@@ -1,11 +1,22 @@
namespace :cache do
+ CLEAR_BATCH_SIZE = 1000
+ 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 = redis_store.scan(
+ cursor.first,
+ match: "#{Gitlab::REDIS_CACHE_NAMESPACE}*",
+ count: CLEAR_BATCH_SIZE
+ )
+
+ keys = cursor.last
+ redis_store.del(*keys) if keys.any?
+
+ break if cursor.first == REDIS_SCAN_START_STOP
end
end
end
diff --git a/lib/tasks/gitlab/check.rake b/lib/tasks/gitlab/check.rake
index 2dc2953e32..0b28b5b131 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
@@ -90,24 +89,6 @@ namespace :gitlab do
end
end
- def check_database_is_not_sqlite
- print "Database is SQLite ... "
-
- database_config_file = Rails.root.join("config", "database.yml")
-
- unless File.read(database_config_file) =~ /adapter:\s+sqlite/
- puts "no".green
- else
- puts "yes".red
- puts "Please fix this by removing the SQLite entry from the database.yml".blue
- for_more_information(
- "https://github.com/gitlabhq/gitlabhq/wiki/Migrate-from-SQLite-to-MySQL",
- see_database_guide
- )
- fix_and_rerun
- end
- end
-
def check_gitlab_config_exists
print "GitLab config exists? ... "
@@ -285,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"
@@ -297,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"
@@ -321,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"
@@ -929,7 +911,7 @@ namespace :gitlab do
end
def check_git_version
- required_version = Gitlab::VersionInfo.new(1, 7, 10)
+ required_version = Gitlab::VersionInfo.new(2, 7, 3)
current_version = Gitlab::VersionInfo.parse(run(%W(#{Gitlab.config.git.bin_path} --version)))
puts "Your git bin path is \"#{Gitlab.config.git.bin_path}\""
diff --git a/scripts/ci/prepare_build.sh b/scripts/ci/prepare_build.sh
deleted file mode 100755
index 864a683a1b..0000000000
--- a/scripts/ci/prepare_build.sh
+++ /dev/null
@@ -1,22 +0,0 @@
-#!/bin/bash
-if [ -f /.dockerinit ]; then
- export FLAGS=(--deployment --path /cache)
-
- apt-get update -qq
- apt-get install -y -qq nodejs
-
- wget -q http://ftp.de.debian.org/debian/pool/main/p/phantomjs/phantomjs_1.9.0-1+b1_amd64.deb
- dpkg -i phantomjs_1.9.0-1+b1_amd64.deb
-
- cp config/database.yml.mysql config/database.yml
- sed -i "s/username:.*/username: root/g" config/database.yml
- sed -i "s/password:.*/password:/g" config/database.yml
- sed -i "s/# socket:.*/host: mysql/g" config/database.yml
-else
- export PATH=$HOME/bin:/usr/local/bin:/usr/bin:/bin
-
- cp config/database.yml.mysql config/database.yml
- sed -i "s/username\:.*$/username\: runner/" config/database.yml
- sed -i "s/password\:.*$/password\: 'password'/" config/database.yml
- sed -i "s/gitlab_ci_test/gitlab_ci_test_$((RANDOM/5000))/" config/database.yml
-fi
diff --git a/scripts/prepare_build.sh b/scripts/prepare_build.sh
index 119cc90fc1..b6f076a90c 100755
--- a/scripts/prepare_build.sh
+++ b/scripts/prepare_build.sh
@@ -1,10 +1,16 @@
#!/bin/bash
+
if [ -f /.dockerinit ]; then
- wget -q https://gitlab.com/axil/phantomjs-debian/raw/master/phantomjs_1.9.8-0jessie_amd64.deb
- dpkg -i phantomjs_1.9.8-0jessie_amd64.deb
+ mkdir -p vendor
+ if [ ! -e vendor/phantomjs_1.9.8-0jessie_amd64.deb ]; then
+ wget -q https://gitlab.com/axil/phantomjs-debian/raw/master/phantomjs_1.9.8-0jessie_amd64.deb
+ mv phantomjs_1.9.8-0jessie_amd64.deb vendor/
+ fi
+ dpkg -i vendor/phantomjs_1.9.8-0jessie_amd64.deb
apt-get update -qq
- apt-get install -y -qq libicu-dev libkrb5-dev cmake nodejs postgresql-client mysql-client
+ apt-get -o dir::cache::archives="vendor/apt" install -y -qq --force-yes \
+ libicu-dev libkrb5-dev cmake nodejs postgresql-client mysql-client unzip
cp config/database.yml.mysql config/database.yml
sed -i 's/username:.*/username: root/g' config/database.yml
@@ -13,8 +19,8 @@ if [ -f /.dockerinit ]; then
cp config/resque.yml.example config/resque.yml
sed -i 's/localhost/redis/g' config/resque.yml
- FLAGS=(--deployment --path /cache)
- export FLAGS
+
+ export FLAGS=(--path vendor)
else
export PATH=$HOME/bin:/usr/local/bin:/usr/bin:/bin
cp config/database.yml.mysql config/database.yml
diff --git a/spec/controllers/admin/spam_logs_controller_spec.rb b/spec/controllers/admin/spam_logs_controller_spec.rb
new file mode 100644
index 0000000000..b51b303a71
--- /dev/null
+++ b/spec/controllers/admin/spam_logs_controller_spec.rb
@@ -0,0 +1,37 @@
+require 'spec_helper'
+
+describe Admin::SpamLogsController do
+ let(:admin) { create(:admin) }
+ let(:user) { create(:user) }
+ let!(:first_spam) { create(:spam_log, user: user) }
+ let!(:second_spam) { create(:spam_log, user: user) }
+
+ before do
+ sign_in(admin)
+ end
+
+ describe '#index' do
+ it 'lists all spam logs' do
+ get :index
+
+ expect(response.status).to eq(200)
+ end
+ end
+
+ describe '#destroy' do
+ it 'removes only the spam log when removing log' do
+ expect { delete :destroy, id: first_spam.id }.to change { SpamLog.count }.by(-1)
+ expect(User.find(user.id)).to be_truthy
+ expect(response.status).to eq(200)
+ end
+
+ it 'removes user and his spam logs when removing the user' do
+ delete :destroy, id: first_spam.id, remove_user: true
+
+ expect(flash[:notice]).to eq "User #{user.username} was successfully removed."
+ expect(response.status).to eq(302)
+ expect(SpamLog.count).to eq(0)
+ expect { User.find(user.id) }.to raise_error(ActiveRecord::RecordNotFound)
+ end
+ end
+end
diff --git a/spec/controllers/ci/projects_controller_spec.rb b/spec/controllers/ci/projects_controller_spec.rb
new file mode 100644
index 0000000000..db0748f323
--- /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 7793bf1e42..bbe400dad8 100644
--- a/spec/controllers/commit_controller_spec.rb
+++ b/spec/controllers/commit_controller_spec.rb
@@ -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/groups_controller_spec.rb b/spec/controllers/groups_controller_spec.rb
new file mode 100644
index 0000000000..938e97298b
--- /dev/null
+++ b/spec/controllers/groups_controller_spec.rb
@@ -0,0 +1,23 @@
+require 'rails_helper'
+
+describe GroupsController do
+ describe 'GET index' do
+ context 'as a user' do
+ it 'redirects to Groups Dashboard' do
+ sign_in(create(:user))
+
+ get :index
+
+ expect(response).to redirect_to(dashboard_groups_path)
+ end
+ end
+
+ context 'as a guest' do
+ it 'redirects to Explore Groups' do
+ get :index
+
+ expect(response).to redirect_to(explore_groups_path)
+ end
+ end
+ end
+end
diff --git a/spec/controllers/projects/commit_controller_spec.rb b/spec/controllers/projects/commit_controller_spec.rb
new file mode 100644
index 0000000000..438e776ec4
--- /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/projects/imports_controller_spec.rb b/spec/controllers/projects/imports_controller_spec.rb
index 85d1d1e052..0147bd2b95 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 6aaec224f6..e82fe26c7a 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,
@@ -188,7 +222,7 @@ describe Projects::MergeRequestsController do
expect(response).to render_template('diffs')
end
end
-
+
context 'as json' do
it 'renders the diffs template to a string' do
go format: 'json'
@@ -199,6 +233,32 @@ describe Projects::MergeRequestsController do
end
end
+ describe 'GET diffs with view' do
+ def go(extra_params = {})
+ params = {
+ namespace_id: project.namespace.to_param,
+ project_id: project.to_param,
+ id: merge_request.iid
+ }
+
+ get :diffs, params.merge(extra_params)
+ end
+
+ it 'saves the preferred diff view in a cookie' do
+ go view: 'parallel'
+
+ expect(response.cookies['diff_view']).to eq('parallel')
+ end
+
+ it 'assigns :view param based on cookie' do
+ request.cookies['diff_view'] = 'parallel'
+
+ go
+
+ expect(controller.params[:view]).to eq 'parallel'
+ end
+ end
+
describe 'GET commits' do
def go(format: 'html')
get :commits,
diff --git a/spec/controllers/projects_controller_spec.rb b/spec/controllers/projects_controller_spec.rb
index 665526fde9..6eee4dfe22 100644
--- a/spec/controllers/projects_controller_spec.rb
+++ b/spec/controllers/projects_controller_spec.rb
@@ -86,6 +86,14 @@ describe ProjectsController do
end
end
end
+
+ context "when the url contains .atom" do
+ let(:public_project_with_dot_atom) { build(:project, :public, name: 'my.atom', path: 'my.atom') }
+
+ it 'expect an error creating the project' do
+ expect(public_project_with_dot_atom).not_to be_valid
+ end
+ end
end
describe "#destroy" do
diff --git a/spec/factories.rb b/spec/factories.rb
index 2a81684dfc..d6483ed6ce 100644
--- a/spec/factories.rb
+++ b/spec/factories.rb
@@ -32,6 +32,7 @@ FactoryGirl.define 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
diff --git a/spec/factories/appearances.rb b/spec/factories/appearances.rb
new file mode 100644
index 0000000000..cf2a2b76bc
--- /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/ci/builds.rb b/spec/factories/ci/builds.rb
index d2db77f628..a46466798d 100644
--- a/spec/factories/ci/builds.rb
+++ b/spec/factories/ci/builds.rb
@@ -1,30 +1,3 @@
-# == Schema Information
-#
-# Table name: builds
-#
-# id :integer not null, primary key
-# project_id :integer
-# status :string(255)
-# finished_at :datetime
-# trace :text
-# created_at :datetime
-# updated_at :datetime
-# started_at :datetime
-# runner_id :integer
-# commit_id :integer
-# coverage :float
-# commands :text
-# job_id :integer
-# name :string(255)
-# deploy :boolean default(FALSE)
-# options :text
-# allow_failure :boolean default(FALSE), not null
-# stage :string(255)
-# trigger_request_id :integer
-#
-
-# Read about factories at https://github.com/thoughtbot/factory_girl
-
FactoryGirl.define do
factory :ci_build, class: Ci::Build do
name 'test'
@@ -43,10 +16,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
@@ -60,10 +53,24 @@ FactoryGirl.define do
tag true
end
- factory :ci_build_with_trace do
- after(:create) do |build, evaluator|
+ trait :trace do
+ after(:create) do |build, evaluator|
build.trace = 'BUILD TRACE'
end
end
+
+ trait :artifacts do
+ after(:create) do |build, _|
+ build.artifacts_file =
+ fixture_file_upload(Rails.root.join('spec/fixtures/ci_build_artifacts.zip'),
+ 'application/zip')
+
+ build.artifacts_metadata =
+ fixture_file_upload(Rails.root.join('spec/fixtures/ci_build_artifacts_metadata.gz'),
+ 'application/x-gzip')
+
+ build.save!
+ end
+ end
end
end
diff --git a/spec/factories/ci/runners.rb b/spec/factories/ci/runners.rb
index db759eca9a..265663e845 100644
--- a/spec/factories/ci/runners.rb
+++ b/spec/factories/ci/runners.rb
@@ -25,14 +25,12 @@ FactoryGirl.define do
"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/merge_requests.rb b/spec/factories/merge_requests.rb
index 0c6a881f86..00de7bb529 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
diff --git a/spec/factories/notes.rb b/spec/factories/notes.rb
index 35a20adeef..32c202891d 100644
--- a/spec/factories/notes.rb
+++ b/spec/factories/notes.rb
@@ -34,6 +34,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 +67,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/spam_logs.rb b/spec/factories/spam_logs.rb
new file mode 100644
index 0000000000..d90e5d6bf2
--- /dev/null
+++ b/spec/factories/spam_logs.rb
@@ -0,0 +1,11 @@
+# Read about factories at https://github.com/thoughtbot/factory_girl
+
+FactoryGirl.define do
+ factory :spam_log do
+ user
+ source_ip { FFaker::Internet.ip_v4_address }
+ noteable_type 'Issue'
+ title { FFaker::Lorem.sentence }
+ description { FFaker::Lorem.paragraph(5) }
+ end
+end
diff --git a/spec/factories/todos.rb b/spec/factories/todos.rb
new file mode 100644
index 0000000000..bd85b1d798
--- /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/features/admin/admin_builds_spec.rb b/spec/features/admin/admin_builds_spec.rb
index b955d0b0c4..2e9851fb44 100644
--- a/spec/features/admin/admin_builds_spec.rb
+++ b/spec/features/admin/admin_builds_spec.rb
@@ -18,7 +18,7 @@ describe 'Admin Builds' do
visit admin_builds_path
- expect(page).to have_selector('.project-issuable-filter li.active', text: 'All')
+ expect(page).to have_selector('.nav-links li.active', text: 'All')
expect(page.all('.build-link').size).to eq(4)
expect(page).to have_link 'Cancel all'
end
@@ -28,7 +28,7 @@ describe 'Admin Builds' do
it 'shows a message' do
visit admin_builds_path
- expect(page).to have_selector('.project-issuable-filter li.active', text: 'All')
+ expect(page).to have_selector('.nav-links li.active', text: 'All')
expect(page).to have_content 'No builds to show'
expect(page).not_to have_link 'Cancel all'
end
@@ -44,7 +44,7 @@ describe 'Admin Builds' do
visit admin_builds_path(scope: :running)
- expect(page).to have_selector('.project-issuable-filter li.active', text: 'Running')
+ expect(page).to have_selector('.nav-links li.active', text: 'Running')
expect(page.find('.build-link')).to have_content(build1.id)
expect(page.find('.build-link')).not_to have_content(build2.id)
expect(page.find('.build-link')).not_to have_content(build3.id)
@@ -58,7 +58,7 @@ describe 'Admin Builds' do
visit admin_builds_path(scope: :running)
- expect(page).to have_selector('.project-issuable-filter li.active', text: 'Running')
+ expect(page).to have_selector('.nav-links li.active', text: 'Running')
expect(page).to have_content 'No builds to show'
expect(page).not_to have_link 'Cancel all'
end
@@ -74,7 +74,7 @@ describe 'Admin Builds' do
visit admin_builds_path(scope: :finished)
- expect(page).to have_selector('.project-issuable-filter li.active', text: 'Finished')
+ expect(page).to have_selector('.nav-links li.active', text: 'Finished')
expect(page.find('.build-link')).not_to have_content(build1.id)
expect(page.find('.build-link')).not_to have_content(build2.id)
expect(page.find('.build-link')).to have_content(build3.id)
@@ -88,7 +88,7 @@ describe 'Admin Builds' do
visit admin_builds_path(scope: :finished)
- expect(page).to have_selector('.project-issuable-filter li.active', text: 'Finished')
+ expect(page).to have_selector('.nav-links li.active', text: 'Finished')
expect(page).to have_content 'No builds to show'
expect(page).to have_link 'Cancel all'
end
diff --git a/spec/features/builds_spec.rb b/spec/features/builds_spec.rb
index d37bd10371..6da3a857b3 100644
--- a/spec/features/builds_spec.rb
+++ b/spec/features/builds_spec.rb
@@ -8,7 +8,7 @@ describe "Builds" do
@commit = FactoryGirl.create :ci_commit
@build = FactoryGirl.create :ci_build, commit: @commit
@project = @commit.project
- @project.team << [@user, :master]
+ @project.team << [@user, :developer]
end
describe "GET /:project/builds" do
@@ -18,7 +18,7 @@ describe "Builds" do
visit namespace_project_builds_path(@project.namespace, @project, scope: :running)
end
- it { expect(page).to have_selector('.project-issuable-filter li.active', text: 'Running') }
+ it { expect(page).to have_selector('.nav-links li.active', text: 'Running') }
it { expect(page).to have_link 'Cancel running' }
it { expect(page).to have_content @build.short_sha }
it { expect(page).to have_content @build.ref }
@@ -31,7 +31,7 @@ describe "Builds" do
visit namespace_project_builds_path(@project.namespace, @project, scope: :finished)
end
- it { expect(page).to have_selector('.project-issuable-filter li.active', text: 'Finished') }
+ it { expect(page).to have_selector('.nav-links li.active', text: 'Finished') }
it { expect(page).to have_content 'No builds to show' }
it { expect(page).to have_link 'Cancel running' }
end
@@ -42,7 +42,7 @@ describe "Builds" do
visit namespace_project_builds_path(@project.namespace, @project)
end
- it { expect(page).to have_selector('.project-issuable-filter li.active', text: 'All') }
+ it { expect(page).to have_selector('.nav-links li.active', text: 'All') }
it { expect(page).to have_content @build.short_sha }
it { expect(page).to have_content @build.ref }
it { expect(page).to have_content @build.name }
@@ -57,7 +57,7 @@ describe "Builds" do
click_link "Cancel running"
end
- it { expect(page).to have_selector('.project-issuable-filter li.active', text: 'All') }
+ it { expect(page).to have_selector('.nav-links li.active', text: 'All') }
it { expect(page).to have_content 'canceled' }
it { expect(page).to have_content @build.short_sha }
it { expect(page).to have_content @build.ref }
diff --git a/spec/features/commits_spec.rb b/spec/features/commits_spec.rb
index 5a62da1061..dacaa96d76 100644
--- a/spec/features/commits_spec.rb
+++ b/spec/features/commits_spec.rb
@@ -8,7 +8,6 @@ describe 'Commits' do
describe 'CI' do
before do
login_as :user
- project.team << [@user, :master]
stub_ci_commit_to_return_yaml_file
end
@@ -19,6 +18,10 @@ describe 'Commits' do
context 'commit status is Generic Commit Status' do
let!(:status) { FactoryGirl.create :generic_commit_status, commit: commit }
+ before do
+ project.team << [@user, :reporter]
+ end
+
describe 'Commit builds' do
before do
visit ci_status_path(commit)
@@ -37,83 +40,124 @@ describe 'Commits' do
context 'commit status is Ci Build' do
let!(:build) { FactoryGirl.create :ci_build, commit: commit }
+ let(:artifacts_file) { fixture_file_upload(Rails.root + 'spec/fixtures/banana_sample.gif', 'image/gif') }
- describe 'Project commits' do
+ context 'when logged as developer' do
before do
- visit namespace_project_commits_path(project.namespace, project, :master)
+ project.team << [@user, :developer]
end
- it 'should show build status' do
- page.within("//li[@id='commit-#{commit.short_sha}']") do
- expect(page).to have_css(".ci-status-link")
+ describe 'Project commits' do
+ before do
+ visit namespace_project_commits_path(project.namespace, project, :master)
+ end
+
+ it 'should show build status' do
+ page.within("//li[@id='commit-#{commit.short_sha}']") do
+ expect(page).to have_css(".ci-status-link")
+ end
+ end
+ end
+
+ describe 'Commit builds' do
+ before do
+ visit ci_status_path(commit)
+ end
+
+ it { expect(page).to have_content commit.sha[0..7] }
+ it { expect(page).to have_content commit.git_commit_message }
+ it { expect(page).to have_content commit.git_author_name }
+ end
+
+ context 'Download artifacts' do
+ before do
+ build.update_attributes(artifacts_file: artifacts_file)
+ end
+
+ it do
+ visit ci_status_path(commit)
+ click_on 'Download artifacts'
+ expect(page.response_headers['Content-Type']).to eq(artifacts_file.content_type)
+ end
+ end
+
+ describe 'Cancel all builds' do
+ it 'cancels commit' do
+ visit ci_status_path(commit)
+ click_on 'Cancel running'
+ expect(page).to have_content 'canceled'
+ end
+ end
+
+ describe 'Cancel build' do
+ it 'cancels build' do
+ visit ci_status_path(commit)
+ click_on 'Cancel'
+ expect(page).to have_content 'canceled'
+ end
+ end
+
+ describe '.gitlab-ci.yml not found warning' do
+ context 'ci builds enabled' do
+ it "does not show warning" do
+ visit ci_status_path(commit)
+ expect(page).not_to have_content '.gitlab-ci.yml not found in this commit'
+ end
+
+ it 'shows warning' do
+ stub_ci_commit_yaml_file(nil)
+ visit ci_status_path(commit)
+ expect(page).to have_content '.gitlab-ci.yml not found in this commit'
+ end
+ end
+
+ context 'ci builds disabled' do
+ before do
+ stub_ci_builds_disabled
+ stub_ci_commit_yaml_file(nil)
+ visit ci_status_path(commit)
+ end
+
+ it 'does not show warning' do
+ expect(page).not_to have_content '.gitlab-ci.yml not found in this commit'
+ end
end
end
end
- describe 'Commit builds' do
- before do
- visit ci_status_path(commit)
- end
-
- it { expect(page).to have_content commit.sha[0..7] }
- it { expect(page).to have_content commit.git_commit_message }
- it { expect(page).to have_content commit.git_author_name }
- end
-
- context 'Download artifacts' do
- let(:artifacts_file) { fixture_file_upload(Rails.root + 'spec/fixtures/banana_sample.gif', 'image/gif') }
-
+ context "when logged as reporter" do
before do
+ project.team << [@user, :reporter]
build.update_attributes(artifacts_file: artifacts_file)
+ visit ci_status_path(commit)
end
it do
- visit ci_status_path(commit)
- click_on 'Download artifacts'
- expect(page.response_headers['Content-Type']).to eq(artifacts_file.content_type)
+ expect(page).to have_content commit.sha[0..7]
+ expect(page).to have_content commit.git_commit_message
+ expect(page).to have_content commit.git_author_name
+ expect(page).to have_link('Download artifacts')
+ expect(page).to_not have_link('Cancel running')
+ expect(page).to_not have_link('Retry failed')
end
end
- describe 'Cancel all builds' do
- it 'cancels commit' do
+ context 'when accessing internal project with disallowed access' do
+ before do
+ project.update(
+ visibility_level: Gitlab::VisibilityLevel::INTERNAL,
+ public_builds: false)
+ build.update_attributes(artifacts_file: artifacts_file)
visit ci_status_path(commit)
- click_on 'Cancel running'
- expect(page).to have_content 'canceled'
- end
- end
-
- describe 'Cancel build' do
- it 'cancels build' do
- visit ci_status_path(commit)
- click_on 'Cancel'
- expect(page).to have_content 'canceled'
- end
- end
-
- describe '.gitlab-ci.yml not found warning' do
- context 'ci builds enabled' do
- it "does not show warning" do
- visit ci_status_path(commit)
- expect(page).not_to have_content '.gitlab-ci.yml not found in this commit'
- end
-
- it 'shows warning' do
- stub_ci_commit_yaml_file(nil)
- visit ci_status_path(commit)
- expect(page).to have_content '.gitlab-ci.yml not found in this commit'
- end
end
- context 'ci builds disabled' do
- before do
- stub_ci_builds_disabled
- stub_ci_commit_yaml_file(nil)
- visit ci_status_path(commit)
- end
-
- it 'does not show warning' do
- expect(page).not_to have_content '.gitlab-ci.yml not found in this commit'
- end
+ it do
+ expect(page).to have_content commit.sha[0..7]
+ expect(page).to have_content commit.git_commit_message
+ expect(page).to have_content commit.git_author_name
+ expect(page).to_not have_link('Download artifacts')
+ expect(page).to_not have_link('Cancel running')
+ expect(page).to_not have_link('Retry failed')
end
end
end
diff --git a/spec/features/login_spec.rb b/spec/features/login_spec.rb
index 2451e56fe7..dac9205449 100644
--- a/spec/features/login_spec.rb
+++ b/spec/features/login_spec.rb
@@ -112,10 +112,10 @@ feature 'Login', feature: true do
context 'within the grace period' do
it 'redirects to two-factor configuration page' do
expect(current_path).to eq new_profile_two_factor_auth_path
- expect(page).to have_content('You must configure Two-Factor Authentication in your account until')
+ expect(page).to have_content('You must enable Two-factor Authentication for your account before')
end
- it 'two-factor configuration is skippable' do
+ it 'disallows skipping two-factor configuration' do
expect(current_path).to eq new_profile_two_factor_auth_path
click_link 'Configure it later'
@@ -128,10 +128,10 @@ feature 'Login', feature: true do
it 'redirects to two-factor configuration page' do
expect(current_path).to eq new_profile_two_factor_auth_path
- expect(page).to have_content('You must configure Two-Factor Authentication in your account.')
+ expect(page).to have_content('You must enable Two-factor Authentication for your account.')
end
- it 'two-factor configuration is not skippable' do
+ it 'disallows skipping two-factor configuration' do
expect(current_path).to eq new_profile_two_factor_auth_path
expect(page).not_to have_link('Configure it later')
end
@@ -146,7 +146,7 @@ feature 'Login', feature: true do
it 'redirects to two-factor configuration page' do
expect(current_path).to eq new_profile_two_factor_auth_path
- expect(page).to have_content('You must configure Two-Factor Authentication in your account.')
+ expect(page).to have_content('You must enable Two-factor Authentication for your account.')
end
end
end
diff --git a/spec/features/notes_on_merge_requests_spec.rb b/spec/features/notes_on_merge_requests_spec.rb
index f0fc6916c4..1a360cd1eb 100644
--- a/spec/features/notes_on_merge_requests_spec.rb
+++ b/spec/features/notes_on_merge_requests_spec.rb
@@ -167,7 +167,7 @@ describe 'Comments', feature: true do
end
it 'should be removed when canceled' do
- page.within(".diff-file form[rel$='#{line_code}']") do
+ page.within(".diff-file form[id$='#{line_code}']") do
find('.js-close-discussion-note-form').trigger('click')
end
diff --git a/spec/features/projects_spec.rb b/spec/features/projects_spec.rb
index 9a01c89ae2..ed97b6cb57 100644
--- a/spec/features/projects_spec.rb
+++ b/spec/features/projects_spec.rb
@@ -82,7 +82,26 @@ feature 'Project', feature: true do
it 'click project-settings and find leave project' do
find('#project-settings-button').click
- expect(page).to have_link('Leave Project')
+ expect(page).to have_link('Leave Project')
+ end
+ end
+
+ describe 'project title' do
+ include WaitForAjax
+
+ let(:user) { create(:user) }
+ let(:project) { create(:project, namespace: user.namespace) }
+
+ before do
+ login_with(user)
+ project.team.add_user(user, Gitlab::Access::MASTER)
+ visit namespace_project_path(project.namespace, project)
+ end
+
+ it 'click toggle and show dropdown', js: true do
+ find('.js-projects-dropdown-toggle').click
+ wait_for_ajax
+ expect(page).to have_css('.select2-results li', count: 1)
end
end
diff --git a/spec/features/runners_spec.rb b/spec/features/runners_spec.rb
index d97831aae1..e8886e7edf 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/features/security/project/public_access_spec.rb b/spec/features/security/project/public_access_spec.rb
index 655d2c8b7d..b98476f854 100644
--- a/spec/features/security/project/public_access_spec.rb
+++ b/spec/features/security/project/public_access_spec.rb
@@ -96,6 +96,60 @@ describe "Public Project Access", feature: true do
it { is_expected.to be_denied_for :visitor }
end
+ describe "GET /:project_path/builds" do
+ subject { namespace_project_builds_path(project.namespace, project) }
+
+ context "when allowed for public" do
+ before { project.update(public_builds: true) }
+
+ it { is_expected.to be_allowed_for master }
+ it { is_expected.to be_allowed_for reporter }
+ it { is_expected.to be_allowed_for :admin }
+ it { is_expected.to be_allowed_for guest }
+ it { is_expected.to be_allowed_for :user }
+ it { is_expected.to be_allowed_for :visitor }
+ end
+
+ context "when disallowed for public" do
+ before { project.update(public_builds: false) }
+
+ it { is_expected.to be_allowed_for master }
+ it { is_expected.to be_allowed_for reporter }
+ it { is_expected.to be_allowed_for :admin }
+ it { is_expected.to be_denied_for guest }
+ it { is_expected.to be_denied_for :user }
+ it { is_expected.to be_denied_for :visitor }
+ end
+ end
+
+ describe "GET /:project_path/builds/:id" do
+ let(:commit) { create(:ci_commit, project: project) }
+ let(:build) { create(:ci_build, commit: commit) }
+ subject { namespace_project_build_path(project.namespace, project, build.id) }
+
+ context "when allowed for public" do
+ before { project.update(public_builds: true) }
+
+ it { is_expected.to be_allowed_for master }
+ it { is_expected.to be_allowed_for reporter }
+ it { is_expected.to be_allowed_for :admin }
+ it { is_expected.to be_allowed_for guest }
+ it { is_expected.to be_allowed_for :user }
+ it { is_expected.to be_allowed_for :visitor }
+ end
+
+ context "when disallowed for public" do
+ before { project.update(public_builds: false) }
+
+ it { is_expected.to be_allowed_for master }
+ it { is_expected.to be_allowed_for reporter }
+ it { is_expected.to be_allowed_for :admin }
+ it { is_expected.to be_denied_for guest }
+ it { is_expected.to be_denied_for :user }
+ it { is_expected.to be_denied_for :visitor }
+ end
+ end
+
describe "GET /:project_path/blob" do
before do
commit = project.repository.commit
diff --git a/spec/fixtures/logo_sample.svg b/spec/fixtures/logo_sample.svg
new file mode 100644
index 0000000000..883e7e6cf9
--- /dev/null
+++ b/spec/fixtures/logo_sample.svg
@@ -0,0 +1,27 @@
+
+
+
+ Slice 1
+ Created with Sketch.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/spec/fixtures/parallel_diff_result.yml b/spec/fixtures/parallel_diff_result.yml
index a326b651aa..a8b7907d4b 100644
--- a/spec/fixtures/parallel_diff_result.yml
+++ b/spec/fixtures/parallel_diff_result.yml
@@ -55,7 +55,7 @@
:type: new
:number: 9
:text: |
- + raise RuntimeError , "System commands must be given as an array of strings"
+ + raise RuntimeError , "System commands must be given as an array of strings"
:line_code: 2f6fcd96b88b36ce98c38da085c795a27d92a3dd_10_9
- :left:
:type:
diff --git a/spec/helpers/application_helper_spec.rb b/spec/helpers/application_helper_spec.rb
index 30e353148a..f6c1005d26 100644
--- a/spec/helpers/application_helper_spec.rb
+++ b/spec/helpers/application_helper_spec.rb
@@ -293,6 +293,10 @@ describe ApplicationHelper do
describe 'render_markup' do
let(:content) { 'Noël' }
+ let(:user) { create(:user) }
+ before do
+ allow(helper).to receive(:current_user).and_return(user)
+ end
it 'should preserve encoding' do
expect(content.encoding.name).to eq('UTF-8')
diff --git a/spec/helpers/diff_helper_spec.rb b/spec/helpers/diff_helper_spec.rb
index 955d2852cf..14986a74c2 100644
--- a/spec/helpers/diff_helper_spec.rb
+++ b/spec/helpers/diff_helper_spec.rb
@@ -104,8 +104,7 @@ describe DiffHelper do
end
end
- describe 'diff_line_content' do
-
+ describe '#diff_line_content' do
it 'should return non breaking space when line is empty' do
expect(diff_line_content(nil)).to eq(' ')
end
@@ -116,9 +115,19 @@ describe DiffHelper do
expect(diff_line_content(diff_file.diff_lines.first.type)).to eq('match')
expect(diff_file.diff_lines.first.new_pos).to eq(6)
end
+ end
- it 'should return safe HTML' do
- expect(diff_line_content(diff_file.diff_lines.first.text)).to be_html_safe
+ describe "#mark_inline_diffs" do
+ let(:old_line) { %{abc 'def'} }
+ let(:new_line) { %{abc "def"} }
+
+ it "returns strings with marked inline diffs" do
+ marked_old_line, marked_new_line = mark_inline_diffs(old_line, new_line)
+
+ expect(marked_old_line).to eq("abc 'def' ")
+ expect(marked_old_line).to be_html_safe
+ expect(marked_new_line).to eq("abc "def" ")
+ expect(marked_new_line).to be_html_safe
end
end
end
diff --git a/spec/helpers/gitlab_markdown_helper_spec.rb b/spec/helpers/gitlab_markdown_helper_spec.rb
index 9a05b21335..9adcd916ce 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(Book )
+ to eq %Q(Book )
end
end
diff --git a/spec/helpers/labels_helper_spec.rb b/spec/helpers/labels_helper_spec.rb
index 0c8d06b705..4f129eca18 100644
--- a/spec/helpers/labels_helper_spec.rb
+++ b/spec/helpers/labels_helper_spec.rb
@@ -3,7 +3,7 @@ require 'spec_helper'
describe LabelsHelper do
describe 'link_to_label' do
let(:project) { create(:empty_project) }
- let(:label) { create(:label, project: project) }
+ let(:label) { create(:label, project: project) }
context 'with @project set' do
before do
@@ -11,34 +11,31 @@ describe LabelsHelper do
end
it 'uses the instance variable' do
- expect(label).not_to receive(:project)
- link_to_label(label)
+ expect(link_to_label(label)).to match %r{.* }
end
end
context 'without @project set' do
it "uses the label's project" do
- expect(label).to receive(:project).and_return(project)
- link_to_label(label)
+ expect(link_to_label(label)).to match %r{.* }
end
end
- context 'with a named project argument' do
- it 'uses the provided project' do
- arg = double('project')
- expect(arg).to receive(:namespace).and_return('foo')
- expect(arg).to receive(:to_param).and_return('foo')
+ context 'with a project argument' do
+ let(:another_project) { double('project', namespace: 'foo3', to_param: 'bar3') }
- link_to_label(label, project: arg)
+ it 'links to merge requests page' do
+ expect(link_to_label(label, project: another_project)).to match %r{.* }
end
+ end
- it 'takes precedence over other types' do
- @project = project
- expect(@project).not_to receive(:namespace)
- expect(label).not_to receive(:project)
-
- arg = double('project', namespace: 'foo', to_param: 'foo')
- link_to_label(label, project: arg)
+ context 'with a type argument' do
+ ['issue', :issue, 'merge_request', :merge_request].each do |type|
+ context "set to #{type}" do
+ it 'links to correct page' do
+ expect(link_to_label(label, type: type)).to match %r{.* }
+ end
+ end
end
end
@@ -66,5 +63,10 @@ describe LabelsHelper do
it 'uses dark text on light backgrounds' do
expect(text_color_for_bg('#EEEEEE')).to eq('#333333')
end
+
+ it 'supports RGB triplets' do
+ expect(text_color_for_bg('#FFF')).to eq '#333333'
+ expect(text_color_for_bg('#000')).to eq '#FFFFFF'
+ end
end
end
diff --git a/spec/helpers/search_helper_spec.rb b/spec/helpers/search_helper_spec.rb
index f0d553f5f1..601b6915e2 100644
--- a/spec/helpers/search_helper_spec.rb
+++ b/spec/helpers/search_helper_spec.rb
@@ -42,9 +42,9 @@ describe SearchHelper do
expect(search_autocomplete_opts(project.name).size).to eq(1)
end
- it "includes the public group" do
+ it "should not include the public group" do
group = create(:group)
- expect(search_autocomplete_opts(group.name).size).to eq(1)
+ expect(search_autocomplete_opts(group.name).size).to eq(0)
end
context "with a current project" do
diff --git a/spec/javascripts/fixtures/project_title.html.haml b/spec/javascripts/fixtures/project_title.html.haml
new file mode 100644
index 0000000000..e5850b6265
--- /dev/null
+++ b/spec/javascripts/fixtures/project_title.html.haml
@@ -0,0 +1,7 @@
+%h1.title
+ %a
+ GitLab Org
+ %a.project-item-select-holder{href: "/gitlab-org/gitlab-test"}
+ GitLab Test
+ %input#project_path.project-item-select.js-projects-dropdown.ajax-project-select{type: "hidden", name: "project_path", "data-include-groups" => "false"}
+ %i.fa.chevron-down.dropdown-toggle-caret.js-projects-dropdown-toggle
diff --git a/spec/javascripts/fixtures/projects.json b/spec/javascripts/fixtures/projects.json
new file mode 100644
index 0000000000..84e8d0ba1e
--- /dev/null
+++ b/spec/javascripts/fixtures/projects.json
@@ -0,0 +1 @@
+[{"id":9,"description":"","default_branch":null,"tag_list":[],"public":true,"archived":false,"visibility_level":20,"ssh_url_to_repo":"phil@localhost:root/test.git","http_url_to_repo":"http://localhost:3000/root/test.git","web_url":"http://localhost:3000/root/test","owner":{"name":"Administrator","username":"root","id":1,"state":"active","avatar_url":"http://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80\u0026d=identicon","web_url":"http://localhost:3000/u/root"},"name":"test","name_with_namespace":"Administrator / test","path":"test","path_with_namespace":"root/test","issues_enabled":true,"merge_requests_enabled":true,"wiki_enabled":true,"builds_enabled":true,"snippets_enabled":false,"created_at":"2016-01-14T19:08:05.364Z","last_activity_at":"2016-01-14T19:08:07.418Z","shared_runners_enabled":true,"creator_id":1,"namespace":{"id":1,"name":"root","path":"root","owner_id":1,"created_at":"2016-01-13T20:19:44.439Z","updated_at":"2016-01-13T20:19:44.439Z","description":"","avatar":null},"avatar_url":null,"star_count":0,"forks_count":0,"open_issues_count":0,"permissions":{"project_access":null,"group_access":null}},{"id":8,"description":"Voluptatem quae nulla eius numquam ullam voluptatibus quia modi.","default_branch":"master","tag_list":[],"public":false,"archived":false,"visibility_level":0,"ssh_url_to_repo":"phil@localhost:h5bp/html5-boilerplate.git","http_url_to_repo":"http://localhost:3000/h5bp/html5-boilerplate.git","web_url":"http://localhost:3000/h5bp/html5-boilerplate","name":"Html5 Boilerplate","name_with_namespace":"H5bp / Html5 Boilerplate","path":"html5-boilerplate","path_with_namespace":"h5bp/html5-boilerplate","issues_enabled":true,"merge_requests_enabled":true,"wiki_enabled":true,"builds_enabled":true,"snippets_enabled":false,"created_at":"2016-01-13T20:19:57.525Z","last_activity_at":"2016-01-13T20:27:57.280Z","shared_runners_enabled":true,"creator_id":1,"namespace":{"id":5,"name":"H5bp","path":"h5bp","owner_id":null,"created_at":"2016-01-13T20:19:57.239Z","updated_at":"2016-01-13T20:19:57.239Z","description":"Tempore accusantium possimus aut libero.","avatar":{"url":null}},"avatar_url":null,"star_count":0,"forks_count":0,"open_issues_count":5,"permissions":{"project_access":{"access_level":10,"notification_level":3},"group_access":{"access_level":50,"notification_level":3}}},{"id":7,"description":"Modi odio mollitia dolorem qui.","default_branch":"master","tag_list":[],"public":false,"archived":false,"visibility_level":0,"ssh_url_to_repo":"phil@localhost:twitter/typeahead-js.git","http_url_to_repo":"http://localhost:3000/twitter/typeahead-js.git","web_url":"http://localhost:3000/twitter/typeahead-js","name":"Typeahead.Js","name_with_namespace":"Twitter / Typeahead.Js","path":"typeahead-js","path_with_namespace":"twitter/typeahead-js","issues_enabled":true,"merge_requests_enabled":true,"wiki_enabled":true,"builds_enabled":true,"snippets_enabled":false,"created_at":"2016-01-13T20:19:56.212Z","last_activity_at":"2016-01-13T20:27:51.496Z","shared_runners_enabled":true,"creator_id":1,"namespace":{"id":4,"name":"Twitter","path":"twitter","owner_id":null,"created_at":"2016-01-13T20:19:54.480Z","updated_at":"2016-01-13T20:19:54.480Z","description":"Id voluptatem ipsa maiores omnis repudiandae et et.","avatar":{"url":null}},"avatar_url":null,"star_count":0,"forks_count":0,"open_issues_count":4,"permissions":{"project_access":null,"group_access":{"access_level":10,"notification_level":3}}},{"id":6,"description":"Omnis asperiores ipsa et beatae quidem necessitatibus quia.","default_branch":"master","tag_list":[],"public":true,"archived":false,"visibility_level":20,"ssh_url_to_repo":"phil@localhost:twitter/flight.git","http_url_to_repo":"http://localhost:3000/twitter/flight.git","web_url":"http://localhost:3000/twitter/flight","name":"Flight","name_with_namespace":"Twitter / Flight","path":"flight","path_with_namespace":"twitter/flight","issues_enabled":true,"merge_requests_enabled":true,"wiki_enabled":true,"builds_enabled":true,"snippets_enabled":false,"created_at":"2016-01-13T20:19:54.754Z","last_activity_at":"2016-01-13T20:27:50.502Z","shared_runners_enabled":true,"creator_id":1,"namespace":{"id":4,"name":"Twitter","path":"twitter","owner_id":null,"created_at":"2016-01-13T20:19:54.480Z","updated_at":"2016-01-13T20:19:54.480Z","description":"Id voluptatem ipsa maiores omnis repudiandae et et.","avatar":{"url":null}},"avatar_url":null,"star_count":0,"forks_count":0,"open_issues_count":4,"permissions":{"project_access":null,"group_access":{"access_level":10,"notification_level":3}}},{"id":5,"description":"Voluptatem commodi voluptate placeat architecto beatae illum dolores fugiat.","default_branch":"master","tag_list":[],"public":false,"archived":false,"visibility_level":0,"ssh_url_to_repo":"phil@localhost:gitlab-org/gitlab-test.git","http_url_to_repo":"http://localhost:3000/gitlab-org/gitlab-test.git","web_url":"http://localhost:3000/gitlab-org/gitlab-test","name":"Gitlab Test","name_with_namespace":"Gitlab Org / Gitlab Test","path":"gitlab-test","path_with_namespace":"gitlab-org/gitlab-test","issues_enabled":true,"merge_requests_enabled":true,"wiki_enabled":true,"builds_enabled":true,"snippets_enabled":false,"created_at":"2016-01-13T20:19:53.202Z","last_activity_at":"2016-01-13T20:27:41.626Z","shared_runners_enabled":true,"creator_id":1,"namespace":{"id":3,"name":"Gitlab Org","path":"gitlab-org","owner_id":null,"created_at":"2016-01-13T20:19:48.851Z","updated_at":"2016-01-13T20:19:48.851Z","description":"Magni mollitia quod quidem soluta nesciunt impedit.","avatar":{"url":null}},"avatar_url":null,"star_count":0,"forks_count":0,"open_issues_count":5,"permissions":{"project_access":null,"group_access":{"access_level":50,"notification_level":3}}},{"id":4,"description":"Aut molestias quas est ut aperiam officia quod libero.","default_branch":"master","tag_list":[],"public":true,"archived":false,"visibility_level":20,"ssh_url_to_repo":"phil@localhost:gitlab-org/gitlab-shell.git","http_url_to_repo":"http://localhost:3000/gitlab-org/gitlab-shell.git","web_url":"http://localhost:3000/gitlab-org/gitlab-shell","name":"Gitlab Shell","name_with_namespace":"Gitlab Org / Gitlab Shell","path":"gitlab-shell","path_with_namespace":"gitlab-org/gitlab-shell","issues_enabled":true,"merge_requests_enabled":true,"wiki_enabled":true,"builds_enabled":true,"snippets_enabled":false,"created_at":"2016-01-13T20:19:51.882Z","last_activity_at":"2016-01-13T20:27:35.678Z","shared_runners_enabled":true,"creator_id":1,"namespace":{"id":3,"name":"Gitlab Org","path":"gitlab-org","owner_id":null,"created_at":"2016-01-13T20:19:48.851Z","updated_at":"2016-01-13T20:19:48.851Z","description":"Magni mollitia quod quidem soluta nesciunt impedit.","avatar":{"url":null}},"avatar_url":null,"star_count":0,"forks_count":0,"open_issues_count":5,"permissions":{"project_access":{"access_level":20,"notification_level":3},"group_access":{"access_level":50,"notification_level":3}}},{"id":3,"description":"Excepturi molestiae quia repellendus omnis est illo illum eligendi.","default_branch":"master","tag_list":[],"public":true,"archived":false,"visibility_level":20,"ssh_url_to_repo":"phil@localhost:gitlab-org/gitlab-ci.git","http_url_to_repo":"http://localhost:3000/gitlab-org/gitlab-ci.git","web_url":"http://localhost:3000/gitlab-org/gitlab-ci","name":"Gitlab Ci","name_with_namespace":"Gitlab Org / Gitlab Ci","path":"gitlab-ci","path_with_namespace":"gitlab-org/gitlab-ci","issues_enabled":true,"merge_requests_enabled":true,"wiki_enabled":true,"builds_enabled":true,"snippets_enabled":false,"created_at":"2016-01-13T20:19:50.346Z","last_activity_at":"2016-01-13T20:27:30.115Z","shared_runners_enabled":true,"creator_id":1,"namespace":{"id":3,"name":"Gitlab Org","path":"gitlab-org","owner_id":null,"created_at":"2016-01-13T20:19:48.851Z","updated_at":"2016-01-13T20:19:48.851Z","description":"Magni mollitia quod quidem soluta nesciunt impedit.","avatar":{"url":null}},"avatar_url":null,"star_count":0,"forks_count":0,"open_issues_count":3,"permissions":{"project_access":null,"group_access":{"access_level":50,"notification_level":3}}},{"id":2,"description":"Adipisci quaerat dignissimos enim sed ipsam dolorem quia.","default_branch":"master","tag_list":[],"public":false,"archived":false,"visibility_level":10,"ssh_url_to_repo":"phil@localhost:gitlab-org/gitlab-ce.git","http_url_to_repo":"http://localhost:3000/gitlab-org/gitlab-ce.git","web_url":"http://localhost:3000/gitlab-org/gitlab-ce","name":"Gitlab Ce","name_with_namespace":"Gitlab Org / Gitlab Ce","path":"gitlab-ce","path_with_namespace":"gitlab-org/gitlab-ce","issues_enabled":true,"merge_requests_enabled":true,"wiki_enabled":true,"builds_enabled":true,"snippets_enabled":false,"created_at":"2016-01-13T20:19:49.065Z","last_activity_at":"2016-01-13T20:26:58.454Z","shared_runners_enabled":true,"creator_id":1,"namespace":{"id":3,"name":"Gitlab Org","path":"gitlab-org","owner_id":null,"created_at":"2016-01-13T20:19:48.851Z","updated_at":"2016-01-13T20:19:48.851Z","description":"Magni mollitia quod quidem soluta nesciunt impedit.","avatar":{"url":null}},"avatar_url":null,"star_count":0,"forks_count":0,"open_issues_count":5,"permissions":{"project_access":{"access_level":30,"notification_level":3},"group_access":{"access_level":50,"notification_level":3}}},{"id":1,"description":"Vel voluptatem maxime saepe ex quia.","default_branch":"master","tag_list":[],"public":false,"archived":false,"visibility_level":0,"ssh_url_to_repo":"phil@localhost:documentcloud/underscore.git","http_url_to_repo":"http://localhost:3000/documentcloud/underscore.git","web_url":"http://localhost:3000/documentcloud/underscore","name":"Underscore","name_with_namespace":"Documentcloud / Underscore","path":"underscore","path_with_namespace":"documentcloud/underscore","issues_enabled":true,"merge_requests_enabled":true,"wiki_enabled":true,"builds_enabled":true,"snippets_enabled":false,"created_at":"2016-01-13T20:19:45.862Z","last_activity_at":"2016-01-13T20:25:03.106Z","shared_runners_enabled":true,"creator_id":1,"namespace":{"id":2,"name":"Documentcloud","path":"documentcloud","owner_id":null,"created_at":"2016-01-13T20:19:44.464Z","updated_at":"2016-01-13T20:19:44.464Z","description":"Aut impedit perferendis fuga et ipsa repellat cupiditate et.","avatar":{"url":null}},"avatar_url":null,"star_count":0,"forks_count":0,"open_issues_count":5,"permissions":{"project_access":null,"group_access":{"access_level":50,"notification_level":3}}}]
diff --git a/spec/javascripts/project_title_spec.js.coffee b/spec/javascripts/project_title_spec.js.coffee
new file mode 100644
index 0000000000..47c7b7febe
--- /dev/null
+++ b/spec/javascripts/project_title_spec.js.coffee
@@ -0,0 +1,46 @@
+#= require select2
+#= require api
+#= require project_select
+#= require project
+
+window.gon = {}
+window.gon.api_version = 'v3'
+
+describe 'Project Title', ->
+ fixture.preload('project_title.html')
+ fixture.preload('projects.json')
+
+ beforeEach ->
+ fixture.load('project_title.html')
+ @project = new Project()
+
+ spyOn(@project, 'changeProject').and.callFake (url) ->
+ window.current_project_url = url
+
+ describe 'project list', ->
+ beforeEach =>
+ @projects_data = fixture.load('projects.json')[0]
+
+ spyOn(jQuery, 'ajax').and.callFake (req) =>
+ expect(req.url).toBe('/api/v3/projects.json')
+ d = $.Deferred()
+ d.resolve @projects_data
+ d.promise()
+
+ it 'to show on toggle click', =>
+ $('.js-projects-dropdown-toggle').click()
+
+ expect($('.title .select2-container').hasClass('select2-dropdown-open')).toBe(true)
+ expect($('.ajax-project-dropdown li').length).toBe(@projects_data.length)
+
+ it 'hide dropdown', ->
+ $("#select2-drop-mask").click()
+
+ expect($('.title .select2-container').hasClass('select2-dropdown-open')).toBe(false)
+
+ it 'change project when clicking item', ->
+ $('.js-projects-dropdown-toggle').click()
+ $('.ajax-project-dropdown li:nth-child(2)').trigger('mouseup')
+
+ expect($('.title .select2-container').hasClass('select2-dropdown-open')).toBe(false)
+ expect(window.current_project_url).toBe('http://localhost:3000/h5bp/html5-boilerplate')
diff --git a/spec/lib/banzai/filter/commit_reference_filter_spec.rb b/spec/lib/banzai/filter/commit_reference_filter_spec.rb
index 473534ba68..63a32d9d45 100644
--- a/spec/lib/banzai/filter/commit_reference_filter_spec.rb
+++ b/spec/lib/banzai/filter/commit_reference_filter_spec.rb
@@ -21,7 +21,7 @@ describe Banzai::Filter::CommitReferenceFilter, lib: true do
let(:reference) { commit.id }
# Let's test a variety of commit SHA sizes just to be paranoid
- [6, 8, 12, 18, 20, 32, 40].each do |size|
+ [7, 8, 12, 18, 20, 32, 40].each do |size|
it "links to a valid reference of #{size} characters" do
doc = reference_filter("See #{reference[0...size]}")
@@ -35,7 +35,7 @@ describe Banzai::Filter::CommitReferenceFilter, lib: true do
doc = reference_filter("See #{commit.id}")
expect(doc.text).to eq "See #{commit.short_id}"
- doc = reference_filter("See #{commit.id[0...6]}")
+ doc = reference_filter("See #{commit.id[0...7]}")
expect(doc.text).to eq "See #{commit.short_id}"
end
diff --git a/spec/lib/banzai/filter/emoji_filter_spec.rb b/spec/lib/banzai/filter/emoji_filter_spec.rb
index cf31405815..b5b38cf0c8 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(':heart:
')
- 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(':+1:
')
- 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/sanitization_filter_spec.rb b/spec/lib/banzai/filter/sanitization_filter_spec.rb
index 760d60a419..e14a6dbf92 100644
--- a/spec/lib/banzai/filter/sanitization_filter_spec.rb
+++ b/spec/lib/banzai/filter/sanitization_filter_spec.rb
@@ -75,6 +75,11 @@ describe Banzai::Filter::SanitizationFilter, lib: true do
expect(filter(act).to_html).to eq exp
end
+ it 'allows `abbr` elements' do
+ exp = act = %q{HTML }
+ expect(filter(act).to_html).to eq exp
+ end
+
it 'removes `rel` attribute from `a` elements' do
act = %q{Link }
exp = %q{Link }
@@ -172,26 +177,4 @@ describe Banzai::Filter::SanitizationFilter, lib: true do
expect(act.to_html).to eq exp
end
end
-
- context 'when inline_sanitization is true' do
- it 'uses a stricter whitelist' do
- doc = filter('Description ', inline_sanitization: true)
- expect(doc.to_html.strip).to eq 'Description'
- end
-
- %w(pre code img ol ul li).each do |elem|
- it "removes '#{elem}' elements" do
- act = "<#{elem}>Description#{elem}>"
- expect(filter(act, inline_sanitization: true).to_html.strip).
- to eq 'Description'
- end
- end
-
- %w(b i strong em a ins del sup sub p).each do |elem|
- it "still allows '#{elem}' elements" do
- exp = act = "<#{elem}>Description#{elem}>"
- expect(filter(act, inline_sanitization: true).to_html).to eq exp
- end
- end
- end
end
diff --git a/spec/lib/banzai/pipeline/description_pipeline_spec.rb b/spec/lib/banzai/pipeline/description_pipeline_spec.rb
new file mode 100644
index 0000000000..76f4207181
--- /dev/null
+++ b/spec/lib/banzai/pipeline/description_pipeline_spec.rb
@@ -0,0 +1,37 @@
+require 'rails_helper'
+
+describe Banzai::Pipeline::DescriptionPipeline do
+ def parse(html)
+ # When we pass HTML to Redcarpet, it gets wrapped in `p` tags...
+ # ...except when we pass it pre-wrapped text. Rabble rabble.
+ unwrap = !html.start_with?('')
+
+ output = described_class.to_html(html, project: spy)
+
+ output.gsub!(%r{\A
(.*)
(.*)\z}, '\1\2') if unwrap
+
+ output
+ end
+
+ it 'uses a limited whitelist' do
+ doc = parse('# Description')
+
+ expect(doc.strip).to eq 'Description'
+ end
+
+ %w(pre code img ol ul li).each do |elem|
+ it "removes '#{elem}' elements" do
+ act = "<#{elem}>Description#{elem}>"
+
+ expect(parse(act).strip).to eq 'Description'
+ end
+ end
+
+ %w(b i strong em a ins del sup sub p).each do |elem|
+ it "still allows '#{elem}' elements" do
+ exp = act = "<#{elem}>Description#{elem}>"
+
+ expect(parse(act).strip).to eq exp
+ end
+ end
+end
diff --git a/spec/lib/ci/status_spec.rb b/spec/lib/ci/status_spec.rb
new file mode 100644
index 0000000000..a2eb14f3a9
--- /dev/null
+++ b/spec/lib/ci/status_spec.rb
@@ -0,0 +1,41 @@
+require 'spec_helper'
+
+describe Ci::Status do
+ describe '.get_status' do
+ subject { described_class.get_status(statuses) }
+
+ [:ci_build, :generic_commit_status].each do |type|
+ context "for #{type}" 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) { [create(type, status: :success), create(type, status: :failed)] }
+ it { is_expected.to eq 'failed' }
+ end
+
+ context 'at least one running' do
+ let(:statuses) { [create(type, status: :success), create(type, status: :running)] }
+ it { is_expected.to eq 'running' }
+ end
+
+ context 'at least one pending' do
+ let(:statuses) { [create(type, status: :success), create(type, status: :pending)] }
+ it { is_expected.to eq 'running' }
+ end
+
+ context 'success and failed but allowed to fail' do
+ let(:statuses) { [create(type, status: :success), create(type, status: :failed, allow_failure: true)] }
+ 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
+ end
+ end
+ end
+end
diff --git a/spec/lib/dnsxl_check_spec.rb b/spec/lib/dnsxl_check_spec.rb
deleted file mode 100644
index a35a1be0c9..0000000000
--- a/spec/lib/dnsxl_check_spec.rb
+++ /dev/null
@@ -1,68 +0,0 @@
-require 'spec_helper'
-require 'ostruct'
-
-describe 'DNSXLCheck', lib: true, no_db: true do
- let(:spam_ip) { '127.0.0.2' }
- let(:no_spam_ip) { '127.0.0.3' }
- let(:invalid_ip) { 'a.b.c.d' }
- let!(:dnsxl_check) { DNSXLCheck.create_from_list([OpenStruct.new({ domain: 'test', weight: 1 })]) }
-
- before(:context) do
- class DNSXLCheck::Resolver
- class << self
- alias_method :old_search, :search
- def search(query)
- return false if query.match(/always\.failing\.domain\z/)
- return true if query.match(/\A2\.0\.0\.127\./)
- return false if query.match(/\A3\.0\.0\.127\./)
- end
- end
- end
- end
-
- describe '#test' do
- before do
- dnsxl_check.threshold = 0.75
- dnsxl_check.add_list('always.failing.domain', 1)
- end
-
- context 'when threshold is used' do
- before { dnsxl_check.use_threshold= true }
-
- it { expect(dnsxl_check.test(spam_ip)).to be_falsey }
- end
-
- context 'when threshold is not used' do
- before { dnsxl_check.use_threshold= false }
-
- it { expect(dnsxl_check.test(spam_ip)).to be_truthy }
- end
- end
-
- describe '#test_with_threshold' do
- it { expect{ dnsxl_check.test_with_threshold(invalid_ip) }.to raise_error(ArgumentError) }
-
- it { expect(dnsxl_check.test_with_threshold(spam_ip)).to be_truthy }
- it { expect(dnsxl_check.test_with_threshold(no_spam_ip)).to be_falsey }
- end
-
- describe '#test_strict' do
- before do
- dnsxl_check.threshold = 1
- dnsxl_check.add_list('always.failing.domain', 1)
- end
-
- it { expect{ dnsxl_check.test_strict(invalid_ip) }.to raise_error(ArgumentError) }
-
- it { expect(dnsxl_check.test_with_threshold(spam_ip)).to be_falsey }
- it { expect(dnsxl_check.test_with_threshold(no_spam_ip)).to be_falsey }
- it { expect(dnsxl_check.test_strict(spam_ip)).to be_truthy }
- it { expect(dnsxl_check.test_strict(no_spam_ip)).to be_falsey }
- end
-
- describe '#threshold=' do
- it { expect{ dnsxl_check.threshold = 0 }.to raise_error(ArgumentError) }
- it { expect{ dnsxl_check.threshold = 1.1 }.to raise_error(ArgumentError) }
- it { expect{ dnsxl_check.threshold = 0.5 }.not_to raise_error }
- end
-end
diff --git a/spec/lib/gitlab/akismet_helper_spec.rb b/spec/lib/gitlab/akismet_helper_spec.rb
new file mode 100644
index 0000000000..9858935180
--- /dev/null
+++ b/spec/lib/gitlab/akismet_helper_spec.rb
@@ -0,0 +1,35 @@
+require 'spec_helper'
+
+describe Gitlab::AkismetHelper, type: :helper do
+ let(:project) { create(:project) }
+ let(:user) { create(:user) }
+
+ before do
+ allow(Gitlab.config.gitlab).to receive(:url).and_return(Settings.send(:build_gitlab_url))
+ current_application_settings.akismet_enabled = true
+ current_application_settings.akismet_api_key = '12345'
+ end
+
+ describe '#check_for_spam?' do
+ it 'returns true for non-member' do
+ expect(helper.check_for_spam?(project, user)).to eq(true)
+ end
+
+ it 'returns false for member' do
+ project.team << [user, :guest]
+ expect(helper.check_for_spam?(project, user)).to eq(false)
+ end
+ end
+
+ describe '#is_spam?' do
+ it 'returns true for spam' do
+ environment = {
+ 'REMOTE_ADDR' => '127.0.0.1',
+ 'HTTP_USER_AGENT' => 'Test User Agent'
+ }
+
+ allow_any_instance_of(::Akismet::Client).to receive(:check).and_return([true, true])
+ expect(helper.is_spam?(environment, user, 'Is this spam?')).to eq(true)
+ end
+ end
+end
diff --git a/spec/lib/gitlab/asciidoc_spec.rb b/spec/lib/gitlab/asciidoc_spec.rb
index 6beb21c6d2..736bf78720 100644
--- a/spec/lib/gitlab/asciidoc_spec.rb
+++ b/spec/lib/gitlab/asciidoc_spec.rb
@@ -42,22 +42,6 @@ module Gitlab
end
end
- context "with project in context" do
-
- let(:context) { { project: create(:project) } }
-
- it "should filter converted input via HTML pipeline and return result" do
- filtered_html = 'ASCII '
-
- allow(Asciidoctor).to receive(:convert).and_return(html)
- expect(Banzai).to receive(:render)
- .with(html, context.merge(pipeline: :asciidoc))
- .and_return(filtered_html)
-
- expect( render('foo', context) ).to eql filtered_html
- end
- end
-
def render(*args)
described_class.render(*args)
end
diff --git a/spec/lib/gitlab/closing_issue_extractor_spec.rb b/spec/lib/gitlab/closing_issue_extractor_spec.rb
index 99288da1e4..04cf11fc6f 100644
--- a/spec/lib/gitlab/closing_issue_extractor_spec.rb
+++ b/spec/lib/gitlab/closing_issue_extractor_spec.rb
@@ -135,6 +135,17 @@ describe Gitlab::ClosingIssueExtractor, lib: true do
message = "resolve #{reference}"
expect(subject.closed_by_message(message)).to eq([issue])
end
+
+ context 'with an external issue tracker reference' do
+ it 'extracts the referenced issue' do
+ jira_project = create(:jira_project, name: 'JIRA_EXT1')
+ jira_issue = ExternalIssue.new("#{jira_project.name}-1", project: jira_project)
+ closing_issue_extractor = described_class.new jira_project
+ message = "Resolve #{jira_issue.to_reference}"
+
+ expect(closing_issue_extractor.closed_by_message(message)).to eq([jira_issue])
+ end
+ end
end
context "with a cross-project reference" do
diff --git a/spec/lib/gitlab/database_spec.rb b/spec/lib/gitlab/database_spec.rb
index 8461e8ce50..d0a447753b 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).
@@ -14,4 +18,52 @@ describe Gitlab::Database, lib: true do
it { is_expected.to satisfy { |val| val == true || val == false } }
end
+
+ describe '.version' do
+ context "on mysql" do
+ it "extracts the version number" do
+ allow(described_class).to receive(:database_version).
+ and_return("5.7.12-standard")
+
+ expect(described_class.version).to eq '5.7.12-standard'
+ end
+ end
+
+ context "on postgresql" do
+ it "extracts the version number" do
+ allow(described_class).to receive(:database_version).
+ and_return("PostgreSQL 9.4.4 on x86_64-apple-darwin14.3.0")
+
+ expect(described_class.version).to eq '9.4.4'
+ 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/highlight_spec.rb b/spec/lib/gitlab/diff/highlight_spec.rb
index b84a57f357..d19bf4ac84 100644
--- a/spec/lib/gitlab/diff/highlight_spec.rb
+++ b/spec/lib/gitlab/diff/highlight_spec.rb
@@ -9,33 +9,69 @@ describe Gitlab::Diff::Highlight, lib: true do
let(:diff_file) { Gitlab::Diff::File.new(diff, [commit.parent, commit]) }
describe '#highlight' do
- let(:diff_lines) { Gitlab::Diff::Highlight.new(diff_file).highlight }
+ context "with a diff file" do
+ let(:subject) { Gitlab::Diff::Highlight.new(diff_file).highlight }
- it 'should return Gitlab::Diff::Line elements' do
- expect(diff_lines.first).to be_an_instance_of(Gitlab::Diff::Line)
+ it 'should return Gitlab::Diff::Line elements' do
+ expect(subject.first).to be_an_instance_of(Gitlab::Diff::Line)
+ end
+
+ it 'should not modify "match" lines' do
+ expect(subject[0].text).to eq('@@ -6,12 +6,18 @@ module Popen')
+ expect(subject[22].text).to eq('@@ -19,6 +25,7 @@ module Popen')
+ end
+
+ it 'highlights and marks unchanged lines' do
+ code = %Q{ def popen ( cmd , path = nil ) \n}
+
+ expect(subject[2].text).to eq(code)
+ end
+
+ it 'highlights and marks removed lines' do
+ code = %Q{- raise "System commands must be given as an array of strings" \n}
+
+ expect(subject[4].text).to eq(code)
+ end
+
+ it 'highlights and marks added lines' do
+ code = %Q{+ raise RuntimeError , "System commands must be given as an array of strings" \n}
+
+ expect(subject[5].text).to eq(code)
+ end
end
- it 'should not modify "match" lines' do
- expect(diff_lines[0].text).to eq('@@ -6,12 +6,18 @@ module Popen')
- expect(diff_lines[22].text).to eq('@@ -19,6 +25,7 @@ module Popen')
- end
+ context "with diff lines" do
+ let(:subject) { Gitlab::Diff::Highlight.new(diff_file.diff_lines).highlight }
- it 'should highlight unchanged lines' do
- code = %Q{ def popen ( cmd , path = nil ) \n}
+ it 'should return Gitlab::Diff::Line elements' do
+ expect(subject.first).to be_an_instance_of(Gitlab::Diff::Line)
+ end
- expect(diff_lines[2].text).to eq(code)
- end
+ it 'should not modify "match" lines' do
+ expect(subject[0].text).to eq('@@ -6,12 +6,18 @@ module Popen')
+ expect(subject[22].text).to eq('@@ -19,6 +25,7 @@ module Popen')
+ end
- it 'should highlight removed lines' do
- code = %Q{- raise "System commands must be given as an array of strings" \n}
+ it 'marks unchanged lines' do
+ code = %Q{ def popen(cmd, path=nil)}
- expect(diff_lines[4].text).to eq(code)
- end
+ expect(subject[2].text).to eq(code)
+ expect(subject[2].text).not_to be_html_safe
+ end
- it 'should highlight added lines' do
- code = %Q{+ raise RuntimeError , "System commands must be given as an array of strings" \n}
+ it 'marks removed lines' do
+ code = %Q{- raise "System commands must be given as an array of strings"}
- expect(diff_lines[5].text).to eq(code)
+ expect(subject[4].text).to eq(code)
+ expect(subject[4].text).not_to be_html_safe
+ end
+
+ it 'marks added lines' do
+ code = %Q{+ raise RuntimeError, "System commands must be given as an array of strings"}
+
+ expect(subject[5].text).to eq(code)
+ expect(subject[5].text).to be_html_safe
+ end
end
end
end
diff --git a/spec/lib/gitlab/diff/inline_diff_marker_spec.rb b/spec/lib/gitlab/diff/inline_diff_marker_spec.rb
index 6f3276a8b5..ea5c31011f 100644
--- a/spec/lib/gitlab/diff/inline_diff_marker_spec.rb
+++ b/spec/lib/gitlab/diff/inline_diff_marker_spec.rb
@@ -2,14 +2,28 @@ require 'spec_helper'
describe Gitlab::Diff::InlineDiffMarker, lib: true do
describe '#inline_diffs' do
- let(:raw) { "abc 'def'" }
- let(:rich) { %{abc 'def' } }
- let(:inline_diffs) { [2..5] }
- let(:subject) { Gitlab::Diff::InlineDiffMarker.new(raw, rich).mark(inline_diffs) }
+ context "when the rich text is html safe" do
+ let(:raw) { "abc 'def'" }
+ let(:rich) { %{abc 'def' }.html_safe }
+ let(:inline_diffs) { [2..5] }
+ let(:subject) { Gitlab::Diff::InlineDiffMarker.new(raw, rich).mark(inline_diffs) }
- it 'marks the inline diffs' do
- expect(subject).to eq(%{abc 'd ef' })
+ it 'marks the inline diffs' do
+ expect(subject).to eq(%{abc 'd ef' })
+ expect(subject).to be_html_safe
+ end
+ end
+
+ context "when the text text is not html safe" do
+ let(:raw) { "abc 'def'" }
+ let(:inline_diffs) { [2..5] }
+ let(:subject) { Gitlab::Diff::InlineDiffMarker.new(raw).mark(inline_diffs) }
+
+ it 'marks the inline diffs' do
+ expect(subject).to eq(%{abc 'd ef'})
+ expect(subject).to be_html_safe
+ end
end
end
end
diff --git a/spec/lib/gitlab/diff/inline_diff_spec.rb b/spec/lib/gitlab/diff/inline_diff_spec.rb
index 056917df89..95a993d26c 100644
--- a/spec/lib/gitlab/diff/inline_diff_spec.rb
+++ b/spec/lib/gitlab/diff/inline_diff_spec.rb
@@ -1,7 +1,7 @@
require 'spec_helper'
describe Gitlab::Diff::InlineDiff, lib: true do
- describe '#inline_diffs' do
+ describe '.for_lines' do
let(:diff) do
< 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]).to eq(merge_request.hook_attrs)
+ 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]).to eq(merge_request.hook_attrs)
+ 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]).to eq(snippet.hook_attrs)
+ 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 925bc442a9..3a769acfdc 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 3ef6168539..961022b9d1 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/regex_spec.rb b/spec/lib/gitlab/regex_spec.rb
index d67ee423b9..c51b10bdc6 100644
--- a/spec/lib/gitlab/regex_spec.rb
+++ b/spec/lib/gitlab/regex_spec.rb
@@ -21,4 +21,12 @@ describe Gitlab::Regex, lib: true do
it { expect('Dash – is this').to match(Gitlab::Regex.project_name_regex) }
it { expect('?gitlab').not_to match(Gitlab::Regex.project_name_regex) }
end
+
+ describe 'file name regex' do
+ it { expect('foo@bar').to match(Gitlab::Regex.file_name_regex) }
+ end
+
+ describe 'file path regex' do
+ it { expect('foo@/bar').to match(Gitlab::Regex.file_path_regex) }
+ 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 0000000000..de7cd99d49
--- /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/mailers/emails/builds_spec.rb b/spec/mailers/emails/builds_spec.rb
new file mode 100644
index 0000000000..0df89938e9
--- /dev/null
+++ b/spec/mailers/emails/builds_spec.rb
@@ -0,0 +1,65 @@
+require 'spec_helper'
+require 'email_spec'
+require 'mailers/shared/notify'
+
+describe Notify do
+ include EmailSpec::Matchers
+
+ include_context 'gitlab email notification'
+
+ describe 'build notification email' do
+ let(:build) { create(:ci_build) }
+ let(:project) { build.project }
+
+ shared_examples 'build email' do
+ it 'contains name of project' do
+ is_expected.to have_body_text build.project_name
+ end
+
+ it 'contains link to project' do
+ is_expected.to have_body_text namespace_project_path(project.namespace, project)
+ end
+ end
+
+ shared_examples 'an email with X-GitLab headers containing build details' do
+ it 'has X-GitLab-Build* headers' do
+ is_expected.to have_header 'X-GitLab-Build-Id', /#{build.id}/
+ is_expected.to have_header 'X-GitLab-Build-Ref', /#{build.ref}/
+ end
+ end
+
+ describe 'build success' do
+ subject { Notify.build_success_email(build.id, 'wow@example.com') }
+ before { build.success }
+
+ it_behaves_like 'build email'
+ it_behaves_like 'an email with X-GitLab headers containing build details'
+ it_behaves_like 'an email with X-GitLab headers containing project details'
+
+ it 'has header indicating build status' do
+ is_expected.to have_header 'X-GitLab-Build-Status', 'success'
+ end
+
+ it 'has the correct subject' do
+ is_expected.to have_subject /Build success for/
+ end
+ end
+
+ describe 'build fail' do
+ subject { Notify.build_fail_email(build.id, 'wow@example.com') }
+ before { build.drop }
+
+ it_behaves_like 'build email'
+ it_behaves_like 'an email with X-GitLab headers containing build details'
+ it_behaves_like 'an email with X-GitLab headers containing project details'
+
+ it 'has header indicating build status' do
+ is_expected.to have_header 'X-GitLab-Build-Status', 'failed'
+ end
+
+ it 'has the correct subject' do
+ is_expected.to have_subject /Build failed for/
+ end
+ end
+ end
+end
diff --git a/spec/mailers/emails/profile_spec.rb b/spec/mailers/emails/profile_spec.rb
new file mode 100644
index 0000000000..5b575da34f
--- /dev/null
+++ b/spec/mailers/emails/profile_spec.rb
@@ -0,0 +1,107 @@
+require 'spec_helper'
+require 'email_spec'
+require 'mailers/shared/notify'
+
+describe Notify do
+ include EmailSpec::Matchers
+ include_context 'gitlab email notification'
+
+ describe 'profile notifications' do
+ describe 'for new users, the email' do
+ let(:example_site_path) { root_path }
+ let(:new_user) { create(:user, email: new_user_address, created_by_id: 1) }
+ let(:token) { 'kETLwRaayvigPq_x3SNM' }
+
+ subject { Notify.new_user_email(new_user.id, token) }
+
+ it_behaves_like 'an email sent from GitLab'
+ it_behaves_like 'a new user email'
+ it_behaves_like 'it should not have Gmail Actions links'
+ it_behaves_like 'a user cannot unsubscribe through footer link'
+
+ it 'contains the password text' do
+ is_expected.to have_body_text /Click here to set your password/
+ end
+
+ it 'includes a link for user to set password' do
+ params = "reset_password_token=#{token}"
+ is_expected.to have_body_text(
+ %r{http://localhost(:\d+)?/users/password/edit\?#{params}}
+ )
+ end
+
+ it 'explains the reset link expiration' do
+ is_expected.to have_body_text(/This link is valid for \d+ (hours?|days?)/)
+ is_expected.to have_body_text(new_user_password_url)
+ is_expected.to have_body_text(/\?user_email=.*%40.*/)
+ end
+ end
+
+ describe 'for users that signed up, the email' do
+ let(:example_site_path) { root_path }
+ let(:new_user) { create(:user, email: new_user_address, password: "securePassword") }
+
+ subject { Notify.new_user_email(new_user.id) }
+
+ it_behaves_like 'an email sent from GitLab'
+ it_behaves_like 'a new user email'
+ it_behaves_like 'it should not have Gmail Actions links'
+ it_behaves_like 'a user cannot unsubscribe through footer link'
+
+ it 'should not contain the new user\'s password' do
+ is_expected.not_to have_body_text /password/
+ end
+ end
+
+ describe 'user added ssh key' do
+ let(:key) { create(:personal_key) }
+
+ subject { Notify.new_ssh_key_email(key.id) }
+
+ it_behaves_like 'an email sent from GitLab'
+ it_behaves_like 'it should not have Gmail Actions links'
+ it_behaves_like 'a user cannot unsubscribe through footer link'
+
+ it 'is sent to the new user' do
+ is_expected.to deliver_to key.user.email
+ end
+
+ it 'has the correct subject' do
+ is_expected.to have_subject /^SSH key was added to your account$/i
+ end
+
+ it 'contains the new ssh key title' do
+ is_expected.to have_body_text /#{key.title}/
+ end
+
+ it 'includes a link to ssh keys page' do
+ is_expected.to have_body_text /#{profile_keys_path}/
+ end
+ end
+
+ describe 'user added email' do
+ let(:email) { create(:email) }
+
+ subject { Notify.new_email_email(email.id) }
+
+ it_behaves_like 'it should not have Gmail Actions links'
+ it_behaves_like 'a user cannot unsubscribe through footer link'
+
+ it 'is sent to the new user' do
+ is_expected.to deliver_to email.user.email
+ end
+
+ it 'has the correct subject' do
+ is_expected.to have_subject /^Email was added to your account$/i
+ end
+
+ it 'contains the new email address' do
+ is_expected.to have_body_text /#{email.email}/
+ end
+
+ it 'includes a link to emails page' do
+ is_expected.to have_body_text /#{profile_emails_path}/
+ end
+ end
+ end
+end
diff --git a/spec/mailers/notify_spec.rb b/spec/mailers/notify_spec.rb
index 7289e596ef..232a11245a 100644
--- a/spec/mailers/notify_spec.rb
+++ b/spec/mailers/notify_spec.rb
@@ -1,237 +1,13 @@
require 'spec_helper'
require 'email_spec'
+require 'mailers/shared/notify'
describe Notify do
include EmailSpec::Helpers
include EmailSpec::Matchers
include RepoHelpers
- new_user_address = 'newguy@example.com'
-
- let(:gitlab_sender_display_name) { Gitlab.config.gitlab.email_display_name }
- let(:gitlab_sender) { Gitlab.config.gitlab.email_from }
- let(:gitlab_sender_reply_to) { Gitlab.config.gitlab.email_reply_to }
- let(:recipient) { create(:user, email: 'recipient@example.com') }
- let(:project) { create(:project) }
- let(:build) { create(:ci_build) }
-
- before(:each) do
- ActionMailer::Base.deliveries.clear
- email = recipient.emails.create(email: "notifications@example.com")
- recipient.update_attribute(:notification_email, email.email)
- end
-
- shared_examples 'a multiple recipients email' do
- it 'is sent to the given recipient' do
- is_expected.to deliver_to recipient.notification_email
- end
- end
-
- shared_examples 'an email sent from GitLab' do
- it 'is sent from GitLab' do
- sender = subject.header[:from].addrs[0]
- expect(sender.display_name).to eq(gitlab_sender_display_name)
- expect(sender.address).to eq(gitlab_sender)
- end
-
- it 'has a Reply-To address' do
- reply_to = subject.header[:reply_to].addresses
- expect(reply_to).to eq([gitlab_sender_reply_to])
- end
- end
-
- shared_examples 'an email with X-GitLab headers containing project details' do
- it 'has X-GitLab-Project* headers' do
- is_expected.to have_header 'X-GitLab-Project', /#{project.name}/
- is_expected.to have_header 'X-GitLab-Project-Id', /#{project.id}/
- is_expected.to have_header 'X-GitLab-Project-Path', /#{project.path_with_namespace}/
- end
- end
-
- shared_examples 'an email with X-GitLab headers containing build details' do
- it 'has X-GitLab-Build* headers' do
- is_expected.to have_header 'X-GitLab-Build-Id', /#{build.id}/
- is_expected.to have_header 'X-GitLab-Build-Ref', /#{build.ref}/
- end
- end
-
- shared_examples 'an email that contains a header with author username' do
- it 'has X-GitLab-Author header containing author\'s username' do
- is_expected.to have_header 'X-GitLab-Author', user.username
- end
- end
-
- shared_examples 'an email starting a new thread' do |message_id_prefix|
- include_examples 'an email with X-GitLab headers containing project details'
-
- it 'has a discussion identifier' do
- is_expected.to have_header 'Message-ID', /<#{message_id_prefix}(.*)@#{Gitlab.config.gitlab.host}>/
- end
- end
-
- shared_examples 'an answer to an existing thread' do |thread_id_prefix|
- include_examples 'an email with X-GitLab headers containing project details'
-
- it 'has a subject that begins with Re: ' do
- is_expected.to have_subject /^Re: /
- end
-
- it 'has headers that reference an existing thread' do
- is_expected.to have_header 'Message-ID', /<(.*)@#{Gitlab.config.gitlab.host}>/
- is_expected.to have_header 'References', /<#{thread_id_prefix}(.*)@#{Gitlab.config.gitlab.host}>/
- is_expected.to have_header 'In-Reply-To', /<#{thread_id_prefix}(.*)@#{Gitlab.config.gitlab.host}>/
- end
- end
-
- shared_examples 'a new user email' do |user_email, site_path|
- it 'is sent to the new user' do
- is_expected.to deliver_to user_email
- end
-
- it 'has the correct subject' do
- is_expected.to have_subject /^Account was created for you$/i
- end
-
- it 'contains the new user\'s login name' do
- is_expected.to have_body_text /#{user_email}/
- end
-
- it 'includes a link to the site' do
- is_expected.to have_body_text /#{site_path}/
- end
- end
-
- shared_examples 'it should have Gmail Actions links' do
- it { is_expected.to have_body_text /ViewAction/ }
- end
-
- shared_examples 'it should not have Gmail Actions links' do
- it { is_expected.to_not have_body_text /ViewAction/ }
- end
-
- shared_examples 'it should show Gmail Actions View Issue link' do
- it_behaves_like 'it should have Gmail Actions links'
-
- it { is_expected.to have_body_text /View Issue/ }
- end
-
- shared_examples 'it should show Gmail Actions View Merge request link' do
- it_behaves_like 'it should have Gmail Actions links'
-
- it { is_expected.to have_body_text /View Merge request/ }
- end
-
- shared_examples 'it should show Gmail Actions View Commit link' do
- it_behaves_like 'it should have Gmail Actions links'
-
- it { is_expected.to have_body_text /View Commit/ }
- end
-
- shared_examples 'an unsubscribeable thread' do
- it { is_expected.to have_body_text /unsubscribe/ }
- end
-
- shared_examples "a user cannot unsubscribe through footer link" do
- it { is_expected.not_to have_body_text /unsubscribe/ }
- end
-
- describe 'for new users, the email' do
- let(:example_site_path) { root_path }
- let(:new_user) { create(:user, email: new_user_address, created_by_id: 1) }
-
- token = 'kETLwRaayvigPq_x3SNM'
-
- subject { Notify.new_user_email(new_user.id, token) }
-
- it_behaves_like 'an email sent from GitLab'
- it_behaves_like 'a new user email', new_user_address
- it_behaves_like 'it should not have Gmail Actions links'
- it_behaves_like 'a user cannot unsubscribe through footer link'
-
- it 'contains the password text' do
- is_expected.to have_body_text /Click here to set your password/
- end
-
- it 'includes a link for user to set password' do
- params = "reset_password_token=#{token}"
- is_expected.to have_body_text(
- %r{http://localhost(:\d+)?/users/password/edit\?#{params}}
- )
- end
-
- it 'explains the reset link expiration' do
- is_expected.to have_body_text(/This link is valid for \d+ (hours?|days?)/)
- is_expected.to have_body_text(new_user_password_url)
- is_expected.to have_body_text(/\?user_email=.*%40.*/)
- end
- end
-
- describe 'for users that signed up, the email' do
- let(:example_site_path) { root_path }
- let(:new_user) { create(:user, email: new_user_address, password: "securePassword") }
-
- subject { Notify.new_user_email(new_user.id) }
-
- it_behaves_like 'an email sent from GitLab'
- it_behaves_like 'a new user email', new_user_address
- it_behaves_like 'it should not have Gmail Actions links'
- it_behaves_like 'a user cannot unsubscribe through footer link'
-
- it 'should not contain the new user\'s password' do
- is_expected.not_to have_body_text /password/
- end
- end
-
- describe 'user added ssh key' do
- let(:key) { create(:personal_key) }
-
- subject { Notify.new_ssh_key_email(key.id) }
-
- it_behaves_like 'an email sent from GitLab'
- it_behaves_like 'it should not have Gmail Actions links'
- it_behaves_like 'a user cannot unsubscribe through footer link'
-
- it 'is sent to the new user' do
- is_expected.to deliver_to key.user.email
- end
-
- it 'has the correct subject' do
- is_expected.to have_subject /^SSH key was added to your account$/i
- end
-
- it 'contains the new ssh key title' do
- is_expected.to have_body_text /#{key.title}/
- end
-
- it 'includes a link to ssh keys page' do
- is_expected.to have_body_text /#{profile_keys_path}/
- end
- end
-
- describe 'user added email' do
- let(:email) { create(:email) }
-
- subject { Notify.new_email_email(email.id) }
-
- it_behaves_like 'it should not have Gmail Actions links'
- it_behaves_like 'a user cannot unsubscribe through footer link'
-
- it 'is sent to the new user' do
- is_expected.to deliver_to email.user.email
- end
-
- it 'has the correct subject' do
- is_expected.to have_subject /^Email was added to your account$/i
- end
-
- it 'contains the new email address' do
- is_expected.to have_body_text /#{email.email}/
- end
-
- it 'includes a link to emails page' do
- is_expected.to have_body_text /#{profile_emails_path}/
- end
- end
+ include_context 'gitlab email notification'
context 'for a project' do
describe 'items that are assignable, the email' do
@@ -270,6 +46,17 @@ describe Notify do
it 'contains a link to the new issue' do
is_expected.to have_body_text /#{namespace_project_issue_path project.namespace, project, issue}/
end
+
+ context 'when enabled email_author_in_body' do
+ before do
+ allow(current_application_settings).to receive(:email_author_in_body).and_return(true)
+ end
+
+ it 'contains a link to note author' do
+ is_expected.to have_body_text issue.author_name
+ is_expected.to have_body_text /wrote\:/
+ end
+ end
end
describe 'that are new with a description' do
@@ -377,6 +164,17 @@ describe Notify do
it 'has the correct message-id set' do
is_expected.to have_header 'Message-ID', ""
end
+
+ context 'when enabled email_author_in_body' do
+ before do
+ allow(current_application_settings).to receive(:email_author_in_body).and_return(true)
+ end
+
+ it 'contains a link to note author' do
+ is_expected.to have_body_text merge_request.author_name
+ is_expected.to have_body_text /wrote\:/
+ end
+ end
end
describe 'that are new with a description' do
@@ -550,6 +348,21 @@ describe Notify do
it 'contains the message from the note' do
is_expected.to have_body_text /#{note.note}/
end
+
+ it 'not contains note author' do
+ is_expected.not_to have_body_text /wrote\:/
+ end
+
+ context 'when enabled email_author_in_body' do
+ before do
+ allow(current_application_settings).to receive(:email_author_in_body).and_return(true)
+ end
+
+ it 'contains a link to note author' do
+ is_expected.to have_body_text note.author_name
+ is_expected.to have_body_text /wrote\:/
+ end
+ end
end
describe 'on a commit' do
@@ -934,49 +747,4 @@ describe Notify do
end
end
- describe 'build success' do
- before { build.success }
-
- subject { Notify.build_success_email(build.id, 'wow@example.com') }
-
- it_behaves_like 'an email with X-GitLab headers containing build details'
- it_behaves_like 'an email with X-GitLab headers containing project details' do
- let(:project) { build.project }
- end
-
- it 'has header indicating build status' do
- is_expected.to have_header 'X-GitLab-Build-Status', 'success'
- end
-
- it 'has the correct subject' do
- should have_subject /Build success for/
- end
-
- it 'contains name of project' do
- should have_body_text build.project_name
- end
- end
-
- describe 'build fail' do
- before { build.drop }
-
- subject { Notify.build_fail_email(build.id, 'wow@example.com') }
-
- it_behaves_like 'an email with X-GitLab headers containing build details'
- it_behaves_like 'an email with X-GitLab headers containing project details' do
- let(:project) { build.project }
- end
-
- it 'has header indicating build status' do
- is_expected.to have_header 'X-GitLab-Build-Status', 'failed'
- end
-
- it 'has the correct subject' do
- should have_subject /Build failed for/
- end
-
- it 'contains name of project' do
- should have_body_text build.project_name
- end
- end
end
diff --git a/spec/mailers/shared/notify.rb b/spec/mailers/shared/notify.rb
new file mode 100644
index 0000000000..48c851ebbd
--- /dev/null
+++ b/spec/mailers/shared/notify.rb
@@ -0,0 +1,117 @@
+shared_context 'gitlab email notification' do
+ let(:gitlab_sender_display_name) { Gitlab.config.gitlab.email_display_name }
+ let(:gitlab_sender) { Gitlab.config.gitlab.email_from }
+ let(:gitlab_sender_reply_to) { Gitlab.config.gitlab.email_reply_to }
+ let(:recipient) { create(:user, email: 'recipient@example.com') }
+ let(:project) { create(:project) }
+ let(:new_user_address) { 'newguy@example.com' }
+
+ before do
+ ActionMailer::Base.deliveries.clear
+ email = recipient.emails.create(email: "notifications@example.com")
+ recipient.update_attribute(:notification_email, email.email)
+ end
+end
+
+shared_examples 'a multiple recipients email' do
+ it 'is sent to the given recipient' do
+ is_expected.to deliver_to recipient.notification_email
+ end
+end
+
+shared_examples 'an email sent from GitLab' do
+ it 'is sent from GitLab' do
+ sender = subject.header[:from].addrs[0]
+ expect(sender.display_name).to eq(gitlab_sender_display_name)
+ expect(sender.address).to eq(gitlab_sender)
+ end
+
+ it 'has a Reply-To address' do
+ reply_to = subject.header[:reply_to].addresses
+ expect(reply_to).to eq([gitlab_sender_reply_to])
+ end
+end
+
+shared_examples 'an email that contains a header with author username' do
+ it 'has X-GitLab-Author header containing author\'s username' do
+ is_expected.to have_header 'X-GitLab-Author', user.username
+ end
+end
+
+shared_examples 'an email with X-GitLab headers containing project details' do
+ it 'has X-GitLab-Project* headers' do
+ is_expected.to have_header 'X-GitLab-Project', /#{project.name}/
+ is_expected.to have_header 'X-GitLab-Project-Id', /#{project.id}/
+ is_expected.to have_header 'X-GitLab-Project-Path', /#{project.path_with_namespace}/
+ end
+end
+
+shared_examples 'an email starting a new thread' do |message_id_prefix|
+ include_examples 'an email with X-GitLab headers containing project details'
+
+ it 'has a discussion identifier' do
+ is_expected.to have_header 'Message-ID', /<#{message_id_prefix}(.*)@#{Gitlab.config.gitlab.host}>/
+ end
+end
+
+shared_examples 'an answer to an existing thread' do |thread_id_prefix|
+ include_examples 'an email with X-GitLab headers containing project details'
+
+ it 'has a subject that begins with Re: ' do
+ is_expected.to have_subject /^Re: /
+ end
+
+ it 'has headers that reference an existing thread' do
+ is_expected.to have_header 'Message-ID', /<(.*)@#{Gitlab.config.gitlab.host}>/
+ is_expected.to have_header 'References', /<#{thread_id_prefix}(.*)@#{Gitlab.config.gitlab.host}>/
+ is_expected.to have_header 'In-Reply-To', /<#{thread_id_prefix}(.*)@#{Gitlab.config.gitlab.host}>/
+ end
+end
+
+shared_examples 'a new user email' do
+ it 'is sent to the new user' do
+ is_expected.to deliver_to new_user_address
+ end
+
+ it 'has the correct subject' do
+ is_expected.to have_subject /^Account was created for you$/i
+ end
+
+ it 'contains the new user\'s login name' do
+ is_expected.to have_body_text /#{new_user_address}/
+ end
+end
+
+shared_examples 'it should have Gmail Actions links' do
+ it { is_expected.to have_body_text /ViewAction/ }
+end
+
+shared_examples 'it should not have Gmail Actions links' do
+ it { is_expected.to_not have_body_text /ViewAction/ }
+end
+
+shared_examples 'it should show Gmail Actions View Issue link' do
+ it_behaves_like 'it should have Gmail Actions links'
+
+ it { is_expected.to have_body_text /View Issue/ }
+end
+
+shared_examples 'it should show Gmail Actions View Merge request link' do
+ it_behaves_like 'it should have Gmail Actions links'
+
+ it { is_expected.to have_body_text /View Merge request/ }
+end
+
+shared_examples 'it should show Gmail Actions View Commit link' do
+ it_behaves_like 'it should have Gmail Actions links'
+
+ it { is_expected.to have_body_text /View Commit/ }
+end
+
+shared_examples 'an unsubscribeable thread' do
+ it { is_expected.to have_body_text /unsubscribe/ }
+end
+
+shared_examples "a user cannot unsubscribe through footer link" do
+ it { is_expected.not_to have_body_text /unsubscribe/ }
+end
diff --git a/spec/models/abuse_report_spec.rb b/spec/models/abuse_report_spec.rb
index f9be8fcbcf..4799bbaa57 100644
--- a/spec/models/abuse_report_spec.rb
+++ b/spec/models/abuse_report_spec.rb
@@ -26,7 +26,7 @@ RSpec.describe AbuseReport, type: :model do
it { is_expected.to validate_presence_of(:reporter) }
it { is_expected.to validate_presence_of(:user) }
it { is_expected.to validate_presence_of(:message) }
- it { is_expected.to validate_uniqueness_of(:user_id) }
+ it { is_expected.to validate_uniqueness_of(:user_id).with_message('has already been reported') }
end
describe '#remove_user' do
diff --git a/spec/models/appearance_spec.rb b/spec/models/appearance_spec.rb
new file mode 100644
index 0000000000..c5658bd26e
--- /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/application_setting_spec.rb b/spec/models/application_setting_spec.rb
index f4c5888275..b1764d7ac0 100644
--- a/spec/models/application_setting_spec.rb
+++ b/spec/models/application_setting_spec.rb
@@ -66,6 +66,18 @@ describe ApplicationSetting, models: true do
it { is_expected.to allow_value(http).for(:after_sign_out_path) }
it { is_expected.to allow_value(https).for(:after_sign_out_path) }
it { is_expected.not_to allow_value(ftp).for(:after_sign_out_path) }
+
+ it { is_expected.to validate_presence_of(:max_attachment_size) }
+
+ it do
+ is_expected.to validate_numericality_of(:max_attachment_size)
+ .only_integer
+ .is_greater_than(0)
+ end
+
+ it_behaves_like 'an object with email-formated attributes', :admin_notification_email do
+ subject { setting }
+ end
end
context 'restricted signup domains' do
diff --git a/spec/models/build_spec.rb b/spec/models/build_spec.rb
index 606340d87e..e3d3d45365 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 dfc0cc3be1..4dc309a425 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 232760dfeb..e891838672 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 ecf37b40c5..253902512c 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/case_sensitivity_spec.rb b/spec/models/concerns/case_sensitivity_spec.rb
index 25b3f4e50d..92fdc5cd65 100644
--- a/spec/models/concerns/case_sensitivity_spec.rb
+++ b/spec/models/concerns/case_sensitivity_spec.rb
@@ -37,7 +37,7 @@ describe CaseSensitivity, models: true do
with(%q{LOWER("foo"."bar") = LOWER(:value)}, value: 'bar').
and_return(criteria)
- expect(model.iwhere(:'foo.bar' => 'bar')).to eq(criteria)
+ expect(model.iwhere('foo.bar'.to_sym => 'bar')).to eq(criteria)
end
end
@@ -87,8 +87,8 @@ describe CaseSensitivity, models: true do
with(%q{LOWER("foo"."baz") = LOWER(:value)}, value: 'baz').
and_return(final)
- got = model.iwhere(:'foo.bar' => 'bar',
- :'foo.baz' => 'baz')
+ got = model.iwhere('foo.bar'.to_sym => 'bar',
+ 'foo.baz'.to_sym => 'baz')
expect(got).to eq(final)
end
@@ -127,7 +127,7 @@ describe CaseSensitivity, models: true do
with(%q{`foo`.`bar` = :value}, value: 'bar').
and_return(criteria)
- expect(model.iwhere(:'foo.bar' => 'bar')).
+ expect(model.iwhere('foo.bar'.to_sym => 'bar')).
to eq(criteria)
end
end
@@ -178,8 +178,8 @@ describe CaseSensitivity, models: true do
with(%q{`foo`.`baz` = :value}, value: 'baz').
and_return(final)
- got = model.iwhere(:'foo.bar' => 'bar',
- :'foo.baz' => 'baz')
+ got = model.iwhere('foo.bar'.to_sym => 'bar',
+ 'foo.baz'.to_sym => 'baz')
expect(got).to eq(final)
end
diff --git a/spec/models/concerns/issuable_spec.rb b/spec/models/concerns/issuable_spec.rb
index 021d62cdf0..600089802b 100644
--- a/spec/models/concerns/issuable_spec.rb
+++ b/spec/models/concerns/issuable_spec.rb
@@ -69,17 +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(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(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/email_spec.rb b/spec/models/email_spec.rb
new file mode 100644
index 0000000000..a20a614964
--- /dev/null
+++ b/spec/models/email_spec.rb
@@ -0,0 +1,22 @@
+# == Schema Information
+#
+# Table name: emails
+#
+# id :integer not null, primary key
+# user_id :integer not null
+# email :string(255) not null
+# created_at :datetime
+# updated_at :datetime
+#
+
+require 'spec_helper'
+
+describe Email, models: true do
+
+ describe 'validations' do
+ it_behaves_like 'an object with email-formated attributes', :email do
+ subject { build(:email) }
+ end
+ end
+
+end
diff --git a/spec/models/event_spec.rb b/spec/models/event_spec.rb
index 071582b028..ec2a923f91 100644
--- a/spec/models/event_spec.rb
+++ b/spec/models/event_spec.rb
@@ -65,27 +65,6 @@ describe Event, models: true do
it { expect(@event.author).to eq(@user) }
end
- describe '.latest_update_time' do
- describe 'when events are present' do
- let(:time) { Time.utc(2015, 1, 1) }
-
- before do
- create(:closed_issue_event, updated_at: time)
- create(:closed_issue_event, updated_at: time + 5)
- end
-
- it 'returns the latest update time' do
- expect(Event.latest_update_time).to eq(time + 5)
- end
- end
-
- describe 'when no events exist' do
- it 'returns nil' do
- expect(Event.latest_update_time).to be_nil
- end
- end
- end
-
describe '.limit_recent' do
let!(:event1) { create(:closed_issue_event) }
let!(:event2) { create(:closed_issue_event) }
diff --git a/spec/models/external_issue_spec.rb b/spec/models/external_issue_spec.rb
index 6ec6b9037a..9b144dd1ec 100644
--- a/spec/models/external_issue_spec.rb
+++ b/spec/models/external_issue_spec.rb
@@ -10,6 +10,21 @@ describe ExternalIssue, models: true do
it { is_expected.to include_module(Referable) }
end
+ describe '.reference_pattern' do
+ it 'allows underscores in the project name' do
+ expect(ExternalIssue.reference_pattern.match('EXT_EXT-1234')[0]).to eq 'EXT_EXT-1234'
+ end
+
+ it 'allows numbers in the project name' do
+ expect(ExternalIssue.reference_pattern.match('EXT3_EXT-1234')[0]).to eq 'EXT3_EXT-1234'
+ end
+
+ it 'requires the project name to begin with A-Z' do
+ expect(ExternalIssue.reference_pattern.match('3EXT_EXT-1234')).to eq nil
+ expect(ExternalIssue.reference_pattern.match('EXT_EXT-1234')[0]).to eq 'EXT_EXT-1234'
+ end
+ end
+
describe '#to_reference' do
it 'returns a String reference to the object' do
expect(issue.to_reference).to eq issue.id
diff --git a/spec/models/hooks/system_hook_spec.rb b/spec/models/hooks/system_hook_spec.rb
index 138b87a9a0..fd1513cab1 100644
--- a/spec/models/hooks/system_hook_spec.rb
+++ b/spec/models/hooks/system_hook_spec.rb
@@ -36,7 +36,7 @@ describe SystemHook, models: true do
it "project_destroy hook" do
user = create(:user)
project = create(:empty_project, namespace: user.namespace)
- Projects::DestroyService.new(project, user, {}).execute
+ Projects::DestroyService.new(project, user, {}).pending_delete!
expect(WebMock).to have_requested(:post, @system_hook.url).with(
body: /project_destroy/,
headers: { 'Content-Type'=>'application/json', 'X-Gitlab-Event'=>'System Hook' }
@@ -65,7 +65,7 @@ describe SystemHook, models: true do
project = create(:project)
project.team << [user, :master]
expect(WebMock).to have_requested(:post, @system_hook.url).with(
- body: /user_add_to_team/,
+ body: /user_add_to_team/,
headers: { 'Content-Type'=>'application/json', 'X-Gitlab-Event'=>'System Hook' }
).once
end
@@ -76,7 +76,7 @@ describe SystemHook, models: true do
project.team << [user, :master]
project.project_members.destroy_all
expect(WebMock).to have_requested(:post, @system_hook.url).with(
- body: /user_remove_from_team/,
+ body: /user_remove_from_team/,
headers: { 'Content-Type'=>'application/json', 'X-Gitlab-Event'=>'System Hook' }
).once
end
diff --git a/spec/models/member_spec.rb b/spec/models/member_spec.rb
index 2aedca20df..2d8f1cc1ad 100644
--- a/spec/models/member_spec.rb
+++ b/spec/models/member_spec.rb
@@ -31,6 +31,10 @@ describe Member, models: true do
it { is_expected.to validate_presence_of(:source) }
it { is_expected.to validate_inclusion_of(:access_level).in_array(Gitlab::Access.values) }
+ it_behaves_like 'an object with email-formated attributes', :invite_email do
+ subject { build(:project_member) }
+ end
+
context "when an invite email is provided" do
let(:member) { build(:project_member, invite_email: "user@example.com", user: nil) }
@@ -159,7 +163,7 @@ describe Member, models: true do
describe "#generate_invite_token" do
let!(:member) { create(:project_member, invite_email: "user@example.com", user: nil) }
-
+
it "sets the invite token" do
expect { member.generate_invite_token }.to change { member.invite_token}
end
diff --git a/spec/models/merge_request_spec.rb b/spec/models/merge_request_spec.rb
index 291e6200a5..c51f34034d 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'
@@ -137,9 +138,10 @@ describe MergeRequest, models: true do
describe 'detection of issues to be closed' do
let(:issue0) { create :issue, project: subject.project }
let(:issue1) { create :issue, project: subject.project }
- let(:commit0) { double('commit0', closes_issues: [issue0]) }
- let(:commit1) { double('commit1', closes_issues: [issue0]) }
- let(:commit2) { double('commit2', closes_issues: [issue1]) }
+
+ let(:commit0) { double('commit0', safe_message: "Fixes #{issue0.to_reference}") }
+ let(:commit1) { double('commit1', safe_message: "Fixes #{issue0.to_reference}") }
+ let(:commit2) { double('commit2', safe_message: "Fixes #{issue1.to_reference}") }
before do
allow(subject).to receive(:commits).and_return([commit0, commit1, commit2])
@@ -149,7 +151,9 @@ describe MergeRequest, models: true do
allow(subject.project).to receive(:default_branch).
and_return(subject.target_branch)
- expect(subject.closes_issues).to eq([issue0, issue1].sort_by(&:id))
+ closed = subject.closes_issues
+
+ expect(closed).to include(issue0, issue1)
end
it 'only lists issues as to be closed if it targets the default branch' do
@@ -167,17 +171,6 @@ describe MergeRequest, models: true do
expect(subject.closes_issues).to include(issue2)
end
-
- context 'for a project with JIRA integration' do
- let(:issue0) { JiraIssue.new('JIRA-123', subject.project) }
- let(:issue1) { JiraIssue.new('FOOBAR-4567', subject.project) }
-
- it 'returns sorted JiraIssues' do
- allow(subject.project).to receive_messages(default_branch: subject.target_branch)
-
- expect(subject.closes_issues).to eq([issue0, issue1])
- end
- end
end
describe "#work_in_progress?" do
@@ -196,6 +189,11 @@ describe MergeRequest, models: true do
expect(subject).to be_work_in_progress
end
+ it "detects the '[WIP]' prefix" do
+ subject.title = "[WIP]#{subject.title}"
+ expect(subject).to be_work_in_progress
+ end
+
it "doesn't detect WIP for words starting with WIP" do
subject.title = "Wipwap #{subject.title}"
expect(subject).not_to be_work_in_progress
@@ -234,9 +232,15 @@ describe MergeRequest, models: true do
expect(subject.can_remove_source_branch?(user2)).to be_falsey
end
- it "is can be removed in all other cases" do
+ it "can be removed if the last commit is the head of the source branch" do
+ allow(subject.source_project).to receive(:commit).and_return(subject.last_commit)
+
expect(subject.can_remove_source_branch?(user)).to be_truthy
end
+
+ it "cannot be removed if the last commit is not also the head of the source branch" do
+ expect(subject.can_remove_source_branch?(user)).to be_falsey
+ end
end
describe "#reset_merge_when_build_succeeds" do
@@ -251,13 +255,22 @@ 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
diff --git a/spec/models/milestone_spec.rb b/spec/models/milestone_spec.rb
index 30a71987d8..1b1380ce4e 100644
--- a/spec/models/milestone_spec.rb
+++ b/spec/models/milestone_spec.rb
@@ -33,6 +33,20 @@ describe Milestone, models: true do
let(:milestone) { create(:milestone) }
let(:issue) { create(:issue) }
+ describe "unique milestone title per project" do
+ it "shouldn't accept the same title in a project twice" do
+ new_milestone = Milestone.new(project: milestone.project, title: milestone.title)
+ expect(new_milestone).not_to be_valid
+ end
+
+ it "should accept the same title in another project" do
+ project = build(:project)
+ new_milestone = Milestone.new(project: project, title: milestone.title)
+
+ expect(new_milestone).to be_valid
+ end
+ end
+
describe "#percent_complete" do
it "should not count open issues" do
milestone.issues << issue
diff --git a/spec/models/note_spec.rb b/spec/models/note_spec.rb
index 9182b42661..583937ca74 100644
--- a/spec/models/note_spec.rb
+++ b/spec/models/note_spec.rb
@@ -26,6 +26,8 @@ describe Note, models: true do
it { is_expected.to belong_to(:project) }
it { is_expected.to belong_to(:noteable) }
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 a3de23369e..012be3e2df 100644
--- a/spec/models/project_spec.rb
+++ b/spec/models/project_spec.rb
@@ -68,6 +68,7 @@ describe Project, models: true do
it { is_expected.to have_many(:runners) }
it { is_expected.to have_many(:variables) }
it { is_expected.to have_many(:triggers) }
+ it { is_expected.to have_many(:todos).dependent(:destroy) }
end
describe 'modules' do
@@ -102,7 +103,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 +520,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 +584,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 5cd5ae327b..7b63da005f 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]
+ it 'returns Master role' do
+ user = create(:user)
+ group = create(:group)
+ group.add_master(user)
- expect(project.team.human_max_access(user.id)).to eq("Master")
+ 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 c484ae8fc8..b596782f4e 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? }
@@ -232,11 +269,199 @@ describe Repository, models: true do
end
describe 'when there are branches' do
- before do
- allow(repository.raw_repository).to receive(:branch_count).and_return(3)
+ it 'returns true' do
+ expect(repository.raw_repository).to receive(:branch_count).and_return(3)
+
+ expect(subject).to eq(true)
end
- it { is_expected.to eq(true) }
+ it 'caches the output' do
+ expect(repository.raw_repository).to receive(:branch_count).
+ once.
+ and_return(3)
+
+ repository.has_visible_content?
+ repository.has_visible_content?
+ end
+ 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 }
+
+ it 'returns true for an empty repository' do
+ expect(empty_repository.empty?).to eq(true)
+ end
+
+ it 'returns false for a non-empty repository' do
+ expect(repository.empty?).to eq(false)
+ end
+
+ it 'caches the output' do
+ expect(repository.raw_repository).to receive(:empty?).
+ once.
+ and_return(false)
+
+ repository.empty?
+ repository.empty?
+ end
+ end
+
+ describe '#root_ref' do
+ it 'returns a branch name' do
+ expect(repository.root_ref).to be_an_instance_of(String)
+ end
+
+ it 'caches the output' do
+ expect(repository.raw_repository).to receive(:root_ref).
+ once.
+ and_return('master')
+
+ repository.root_ref
+ repository.root_ref
+ end
+ end
+
+ describe '#expire_cache' do
+ it 'expires all caches' do
+ expect(repository).to receive(:expire_branch_cache)
+
+ repository.expire_cache
+ end
+
+ it 'expires the caches for a specific branch' do
+ expect(repository).to receive(:expire_branch_cache).with('master')
+
+ repository.expire_cache('master')
+ end
+
+ it 'expires the emptiness cache 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 cache 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
+ it 'expires the root reference cache' do
+ repository.root_ref
+
+ expect(repository.raw_repository).to receive(:root_ref).
+ once.
+ and_return('foo')
+
+ repository.expire_root_ref_cache
+
+ expect(repository.root_ref).to eq('foo')
+ end
+ end
+
+ describe '#expire_has_visible_content_cache' do
+ it 'expires the visible content cache' do
+ repository.has_visible_content?
+
+ expect(repository.raw_repository).to receive(:branch_count).
+ once.
+ and_return(0)
+
+ repository.expire_has_visible_content_cache
+
+ expect(repository.has_visible_content?).to eq(false)
+ end
+ end
+
+ describe '#expire_branch_ache' do
+ # This method is private but we need it for testing purposes. Sadly there's
+ # no other proper way of testing caching operations.
+ let(:cache) { repository.send(:cache) }
+
+ it 'expires the cache for all branches' do
+ expect(cache).to receive(:expire).
+ at_least(repository.branches.length).
+ times
+
+ repository.expire_branch_cache
+ end
+
+ it 'expires the cache for all branches when the root branch is given' do
+ expect(cache).to receive(:expire).
+ at_least(repository.branches.length).
+ times
+
+ repository.expire_branch_cache(repository.root_ref)
+ end
+
+ it 'expires the cache for a specific branch' do
+ expect(cache).to receive(:expire).once
+
+ repository.expire_branch_cache('foo')
+ 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_merge' do
+ it 'should revert the changes' do
+ repository.revert(user, merge_commit, 'master')
+
+ expect(repository.blob_at_branch('master', 'files/ruby/feature.rb')).not_to be_present
end
end
end
diff --git a/spec/models/spam_log_spec.rb b/spec/models/spam_log_spec.rb
new file mode 100644
index 0000000000..c4ec7625cb
--- /dev/null
+++ b/spec/models/spam_log_spec.rb
@@ -0,0 +1,25 @@
+require 'spec_helper'
+
+describe SpamLog, models: true do
+ describe 'associations' do
+ it { is_expected.to belong_to(:user) }
+ end
+
+ describe 'validations' do
+ it { is_expected.to validate_presence_of(:user) }
+ end
+
+ describe '#remove_user' do
+ it 'blocks the user' do
+ spam_log = build(:spam_log)
+
+ expect { spam_log.remove_user }.to change { spam_log.user.blocked? }.to(true)
+ end
+
+ it 'removes the user' do
+ spam_log = build(:spam_log)
+
+ expect { spam_log.remove_user }.to change { User.count }.by(-1)
+ end
+ end
+end
diff --git a/spec/models/todo_spec.rb b/spec/models/todo_spec.rb
new file mode 100644
index 0000000000..fe9ea7e7d1
--- /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/tree_spec.rb b/spec/models/tree_spec.rb
new file mode 100644
index 0000000000..0737999e12
--- /dev/null
+++ b/spec/models/tree_spec.rb
@@ -0,0 +1,64 @@
+require 'spec_helper'
+
+describe Tree, models: true do
+ let(:repository) { create(:project).repository }
+ let(:sha) { repository.root_ref }
+
+ subject { described_class.new(repository, '54fcc214') }
+
+ describe '#readme' do
+ class FakeBlob
+ attr_reader :name
+
+ def initialize(name)
+ @name = name
+ end
+
+ def readme?
+ name =~ /^readme/i
+ end
+ end
+
+ it 'returns nil when repository does not contains a README file' do
+ files = [FakeBlob.new('file'), FakeBlob.new('license'), FakeBlob.new('copying')]
+ expect(subject).to receive(:blobs).and_return(files)
+
+ expect(subject.readme).to eq nil
+ end
+
+ it 'returns nil when repository does not contains a previewable README file' do
+ files = [FakeBlob.new('file'), FakeBlob.new('README.pages'), FakeBlob.new('README.png')]
+ expect(subject).to receive(:blobs).and_return(files)
+
+ expect(subject.readme).to eq nil
+ end
+
+ it 'returns README when repository contains a previewable README file' do
+ files = [FakeBlob.new('README.png'), FakeBlob.new('README'), FakeBlob.new('file')]
+ expect(subject).to receive(:blobs).and_return(files)
+
+ expect(subject.readme.name).to eq 'README'
+ end
+
+ it 'returns first previewable README when repository contains more than one' do
+ files = [FakeBlob.new('file'), FakeBlob.new('README.md'), FakeBlob.new('README.asciidoc')]
+ expect(subject).to receive(:blobs).and_return(files)
+
+ expect(subject.readme.name).to eq 'README.md'
+ end
+
+ it 'returns first plain text README when repository contains more than one' do
+ files = [FakeBlob.new('file'), FakeBlob.new('README'), FakeBlob.new('README.txt')]
+ expect(subject).to receive(:blobs).and_return(files)
+
+ expect(subject.readme.name).to eq 'README'
+ end
+
+ it 'prioritizes previewable README file over one in plain text' do
+ files = [FakeBlob.new('file'), FakeBlob.new('README'), FakeBlob.new('README.md')]
+ expect(subject).to receive(:blobs).and_return(files)
+
+ expect(subject.readme.name).to eq 'README.md'
+ end
+ end
+end
diff --git a/spec/models/user_spec.rb b/spec/models/user_spec.rb
index 0bef68e288..95188f518c 100644
--- a/spec/models/user_spec.rb
+++ b/spec/models/user_spec.rb
@@ -91,6 +91,8 @@ describe User, models: true do
it { is_expected.to have_many(:assigned_merge_requests).dependent(:destroy) }
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
@@ -118,37 +120,15 @@ describe User, models: true do
it { is_expected.to validate_length_of(:bio).is_within(0..255) }
+ it_behaves_like 'an object with email-formated attributes', :email do
+ subject { build(:user) }
+ end
+
+ it_behaves_like 'an object with email-formated attributes', :public_email, :notification_email do
+ subject { build(:user).tap { |user| user.emails << build(:email, email: email_value) } }
+ end
+
describe 'email' do
- it 'accepts info@example.com' do
- user = build(:user, email: 'info@example.com')
- expect(user).to be_valid
- end
-
- it 'accepts info+test@example.com' do
- user = build(:user, email: 'info+test@example.com')
- expect(user).to be_valid
- end
-
- it "accepts o'reilly@example.com" do
- user = build(:user, email: "o'reilly@example.com")
- expect(user).to be_valid
- end
-
- it 'rejects test@test@example.com' do
- user = build(:user, email: 'test@test@example.com')
- expect(user).to be_invalid
- end
-
- it 'rejects mailto:test@example.com' do
- user = build(:user, email: 'mailto:test@example.com')
- expect(user).to be_invalid
- end
-
- it "rejects lol!'+=?><#$%^&*()@gmail.com" do
- user = build(:user, email: "lol!'+=?><#$%^&*()@gmail.com")
- expect(user).to be_invalid
- end
-
context 'when no signup domains listed' do
before { allow(current_application_settings).to receive(:restricted_signup_domains).and_return([]) }
it 'accepts any email' do
@@ -276,6 +256,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!
@@ -284,6 +265,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/models/wiki_page_spec.rb b/spec/models/wiki_page_spec.rb
index c1b03838aa..ddc49495ed 100644
--- a/spec/models/wiki_page_spec.rb
+++ b/spec/models/wiki_page_spec.rb
@@ -189,6 +189,38 @@ describe WikiPage, models: true do
end
end
+ describe '#historical?' do
+ before do
+ create_page('Update', 'content')
+ @page = wiki.find_page('Update')
+ 3.times { |i| @page.update("content #{i}") }
+ end
+
+ after do
+ destroy_page('Update')
+ end
+
+ it 'returns true when requesting an old version' do
+ old_version = @page.versions.last.to_s
+ old_page = wiki.find_page('Update', old_version)
+
+ expect(old_page.historical?).to eq true
+ end
+
+ it 'returns false when requesting latest version' do
+ latest_version = @page.versions.first.to_s
+ latest_page = wiki.find_page('Update', latest_version)
+
+ expect(latest_page.historical?).to eq false
+ end
+
+ it 'returns false when version is nil' do
+ latest_page = wiki.find_page('Update', nil)
+
+ expect(latest_page.historical?).to eq false
+ end
+ end
+
private
def remove_temp_repo(path)
diff --git a/spec/requests/api/builds_spec.rb b/spec/requests/api/builds_spec.rb
index 8c9f5a382b..175ee861a7 100644
--- a/spec/requests/api/builds_spec.rb
+++ b/spec/requests/api/builds_spec.rb
@@ -4,169 +4,241 @@ 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(: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
- context 'authorized user' do
- context 'user with :manage_builds persmission' do
- it 'should cancel running or pending build' do
- post api("/projects/#{project.id}/builds/#{build.id}/cancel", user)
+ 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
expect(response.status).to eq(201)
expect(project.builds.first.status).to eq('canceled')
end
end
- context 'user without :manage_builds permission' do
- it 'should not cancel build' do
- post api("/projects/#{project.id}/builds/#{build.id}/cancel", user2)
+ context 'user without :update_build permission' do
+ 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
- context 'authorized user' do
- context 'user with :manage_builds persmission' do
- it 'should retry non-running build' do
- post api("/projects/#{project.id}/builds/#{build_canceled.id}/retry", user)
+ 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 permission' do
+ it 'should retry non-running build' do
expect(response.status).to eq(201)
expect(project.builds.first.status).to eq('canceled')
expect(json_response['status']).to eq('pending')
end
end
- context 'user without :manage_builds permission' do
- it 'should not retry build' do
- post api("/projects/#{project.id}/builds/#{build_canceled.id}/retry", user2)
+ context 'user without :update_build permission' do
+ 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 21482fc107..89b554622e 100644
--- a/spec/requests/api/commit_status_spec.rb
+++ b/spec/requests/api/commit_status_spec.rb
@@ -2,18 +2,17 @@ require 'spec_helper'
describe API::CommitStatus, api: true do
include ApiHelpers
- let(:user) { create(:user) }
- let(:user2) { create(:user) }
- let!(:project) { create(:project, creator_id: user.id) }
- let!(:reporter) { create(:project_member, user: user, project: project, access_level: ProjectMember::REPORTER) }
- let!(:guest) { create(:project_member, user: user2, project: project, access_level: ProjectMember::GUEST) }
+ 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) }
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", user) }
+ let(:request) { get api("/projects/#{project.id}/repository/commits/#{commit.id}/statuses", reporter) }
end
context "reporter user" do
@@ -29,7 +28,7 @@ describe API::CommitStatus, api: true do
end
it "should return latest commit statuses" do
- get api("/projects/#{project.id}/repository/commits/#{commit.id}/statuses", user)
+ get api("/projects/#{project.id}/repository/commits/#{commit.id}/statuses", reporter)
expect(response.status).to eq(200)
expect(json_response).to be_an Array
@@ -39,7 +38,7 @@ describe API::CommitStatus, api: true do
end
it "should return all commit statuses" do
- get api("/projects/#{project.id}/repository/commits/#{commit.id}/statuses?all=1", user)
+ get api("/projects/#{project.id}/repository/commits/#{commit.id}/statuses?all=1", reporter)
expect(response.status).to eq(200)
expect(json_response).to be_an Array
@@ -47,7 +46,7 @@ describe API::CommitStatus, api: true do
end
it "should return latest commit statuses for specific ref" do
- get api("/projects/#{project.id}/repository/commits/#{commit.id}/statuses?ref=develop", user)
+ get api("/projects/#{project.id}/repository/commits/#{commit.id}/statuses?ref=develop", reporter)
expect(response.status).to eq(200)
expect(json_response).to be_an Array
@@ -55,7 +54,7 @@ describe API::CommitStatus, api: true do
end
it "should return latest commit statuses for specific name" do
- get api("/projects/#{project.id}/repository/commits/#{commit.id}/statuses?name=coverage", user)
+ get api("/projects/#{project.id}/repository/commits/#{commit.id}/statuses?name=coverage", reporter)
expect(response.status).to eq(200)
expect(json_response).to be_an Array
@@ -65,7 +64,7 @@ describe API::CommitStatus, api: true do
context "guest user" do
it "should not return project commits" do
- get api("/projects/#{project.id}/repository/commits/#{commit.id}/statuses", user2)
+ get api("/projects/#{project.id}/repository/commits/#{commit.id}/statuses", guest)
expect(response.status).to eq(403)
end
end
@@ -81,10 +80,10 @@ describe API::CommitStatus, api: true do
describe 'POST /projects/:id/statuses/:sha' do
let(:post_url) { "/projects/#{project.id}/statuses/#{commit.id}" }
- context 'reporter user' do
+ context 'developer user' do
context 'should create commit status' do
it 'with only required parameters' do
- post api(post_url, user), state: 'success'
+ post api(post_url, developer), state: 'success'
expect(response.status).to eq(201)
expect(json_response['sha']).to eq(commit.id)
expect(json_response['status']).to eq('success')
@@ -95,7 +94,7 @@ describe API::CommitStatus, api: true do
end
it 'with all optional parameters' do
- post api(post_url, user), state: 'success', context: 'coverage', ref: 'develop', target_url: 'url', description: 'test'
+ post api(post_url, developer), state: 'success', context: 'coverage', ref: 'develop', target_url: 'url', description: 'test'
expect(response.status).to eq(201)
expect(json_response['sha']).to eq(commit.id)
expect(json_response['status']).to eq('success')
@@ -108,25 +107,32 @@ describe API::CommitStatus, api: true do
context 'should not create commit status' do
it 'with invalid state' do
- post api(post_url, user), state: 'invalid'
+ post api(post_url, developer), state: 'invalid'
expect(response.status).to eq(400)
end
it 'without state' do
- post api(post_url, user)
+ post api(post_url, developer)
expect(response.status).to eq(400)
end
it 'invalid commit' do
- post api("/projects/#{project.id}/statuses/invalid_sha", user), state: 'running'
+ post api("/projects/#{project.id}/statuses/invalid_sha", developer), state: 'running'
expect(response.status).to eq(404)
end
end
end
+ context 'reporter user' do
+ it 'should not create commit status' do
+ post api(post_url, reporter)
+ expect(response.status).to eq(403)
+ end
+ end
+
context 'guest user' do
it 'should not create commit status' do
- post api(post_url, user2)
+ post api(post_url, guest)
expect(response.status).to eq(403)
end
end
@@ -138,4 +144,10 @@ describe API::CommitStatus, api: true do
end
end
end
+
+ def create_user(access_level)
+ user = create(:user)
+ create(:project_member, user: user, project: project, access_level: access_level)
+ user
+ end
end
diff --git a/spec/requests/api/issues_spec.rb b/spec/requests/api/issues_spec.rb
index 5e65ad18c0..571ea2dae4 100644
--- a/spec/requests/api/issues_spec.rb
+++ b/spec/requests/api/issues_spec.rb
@@ -46,10 +46,10 @@ describe API::API, api: true do
expect(json_response.first['title']).to eq(issue.title)
end
- it "should add pagination headers" do
- get api("/issues?per_page=3", user)
+ it "should add pagination headers and keep query params" do
+ get api("/issues?state=closed&per_page=3", user)
expect(response.headers['Link']).to eq(
- '; rel="first", ; rel="last"'
+ '; rel="first", ; rel="last"' % [user.private_token, user.private_token]
)
end
@@ -241,6 +241,37 @@ describe API::API, api: true do
end
end
+ describe 'POST /projects/:id/issues with spam filtering' do
+ before do
+ Grape::Endpoint.before_each do |endpoint|
+ allow(endpoint).to receive(:check_for_spam?).and_return(true)
+ allow(endpoint).to receive(:is_spam?).and_return(true)
+ end
+ end
+
+ let(:params) do
+ {
+ title: 'new issue',
+ description: 'content here',
+ labels: 'label, label2'
+ }
+ end
+
+ it "should not create a new project issue" do
+ expect { post api("/projects/#{project.id}/issues", user), params }.not_to change(Issue, :count)
+ expect(response.status).to eq(400)
+ expect(json_response['message']).to eq({ "error" => "Spam detected" })
+
+ spam_logs = SpamLog.all
+ expect(spam_logs.count).to eq(1)
+ expect(spam_logs[0].title).to eq('new issue')
+ expect(spam_logs[0].description).to eq('content here')
+ expect(spam_logs[0].user).to eq(user)
+ expect(spam_logs[0].noteable_type).to eq('Issue')
+ expect(spam_logs[0].project_id).to eq(project.id)
+ end
+ end
+
describe "PUT /projects/:id/issues/:issue_id to update only title" do
it "should update a project issue" do
put api("/projects/#{project.id}/issues/#{issue.id}", user),
diff --git a/spec/requests/api/merge_requests_spec.rb b/spec/requests/api/merge_requests_spec.rb
index e194eb93cf..4fd1df2556 100644
--- a/spec/requests/api/merge_requests_spec.rb
+++ b/spec/requests/api/merge_requests_spec.rb
@@ -10,6 +10,7 @@ describe API::API, api: true do
let!(:merge_request_merged) { create(:merge_request, state: "merged", author: user, assignee: user, source_project: project, target_project: project, title: "Merged test", created_at: base_time + 2.seconds) }
let!(:note) { create(:note_on_merge_request, author: user, project: project, noteable: merge_request, note: "a comment on a MR") }
let!(:note2) { create(:note_on_merge_request, author: user, project: project, noteable: merge_request, note: "another comment on a MR") }
+ let(:milestone) { create(:milestone, title: '1.0.0', project: project) }
before do
project.team << [user, :reporters]
@@ -109,12 +110,13 @@ describe API::API, api: true do
end
end
- describe "GET /projects/:id/merge_request/:merge_request_id" do
+ describe "GET /projects/:id/merge_requests/:merge_request_id" do
it "should return merge_request" do
- get api("/projects/#{project.id}/merge_request/#{merge_request.id}", user)
+ get api("/projects/#{project.id}/merge_requests/#{merge_request.id}", user)
expect(response.status).to eq(200)
expect(json_response['title']).to eq(merge_request.title)
expect(json_response['iid']).to eq(merge_request.iid)
+ expect(json_response['merge_status']).to eq('can_be_merged')
end
it 'should return merge_request by iid' do
@@ -126,14 +128,14 @@ describe API::API, api: true do
end
it "should return a 404 error if merge_request_id not found" do
- get api("/projects/#{project.id}/merge_request/999", user)
+ get api("/projects/#{project.id}/merge_requests/999", user)
expect(response.status).to eq(404)
end
end
- describe 'GET /projects/:id/merge_request/:merge_request_id/commits' do
+ describe 'GET /projects/:id/merge_requests/:merge_request_id/commits' do
context 'valid merge request' do
- before { get api("/projects/#{project.id}/merge_request/#{merge_request.id}/commits", user) }
+ before { get api("/projects/#{project.id}/merge_requests/#{merge_request.id}/commits", user) }
let(:commit) { merge_request.commits.first }
it { expect(response.status).to eq 200 }
@@ -143,20 +145,20 @@ describe API::API, api: true do
end
it 'returns a 404 when merge_request_id not found' do
- get api("/projects/#{project.id}/merge_request/999/commits", user)
+ get api("/projects/#{project.id}/merge_requests/999/commits", user)
expect(response.status).to eq(404)
end
end
- describe 'GET /projects/:id/merge_request/:merge_request_id/changes' do
+ describe 'GET /projects/:id/merge_requests/:merge_request_id/changes' do
it 'should return the change information of the merge_request' do
- get api("/projects/#{project.id}/merge_request/#{merge_request.id}/changes", user)
+ get api("/projects/#{project.id}/merge_requests/#{merge_request.id}/changes", user)
expect(response.status).to eq 200
expect(json_response['changes'].size).to eq(merge_request.diffs.size)
end
it 'returns a 404 when merge_request_id not found' do
- get api("/projects/#{project.id}/merge_request/999/changes", user)
+ get api("/projects/#{project.id}/merge_requests/999/changes", user)
expect(response.status).to eq(404)
end
end
@@ -169,10 +171,12 @@ describe API::API, api: true do
source_branch: 'feature_conflict',
target_branch: 'master',
author: user,
- labels: 'label, label2'
+ labels: 'label, label2',
+ milestone_id: milestone.id
expect(response.status).to eq(201)
expect(json_response['title']).to eq('Test merge_request')
expect(json_response['labels']).to eq(['label', 'label2'])
+ expect(json_response['milestone']['id']).to eq(milestone.id)
end
it "should return 422 when source_branch equals target_branch" do
@@ -311,19 +315,19 @@ describe API::API, api: true do
end
end
- describe "PUT /projects/:id/merge_request/:merge_request_id to close MR" do
+ describe "PUT /projects/:id/merge_requests/:merge_request_id to close MR" do
it "should return merge_request" do
- put api("/projects/#{project.id}/merge_request/#{merge_request.id}", user), state_event: "close"
+ put api("/projects/#{project.id}/merge_requests/#{merge_request.id}", user), state_event: "close"
expect(response.status).to eq(200)
expect(json_response['state']).to eq('closed')
end
end
- describe "PUT /projects/:id/merge_request/:merge_request_id/merge" do
+ describe "PUT /projects/:id/merge_requests/:merge_request_id/merge" do
let(:ci_commit) { create(:ci_commit_without_jobs) }
it "should return merge_request in case of success" do
- put api("/projects/#{project.id}/merge_request/#{merge_request.id}/merge", user)
+ put api("/projects/#{project.id}/merge_requests/#{merge_request.id}/merge", user)
expect(response.status).to eq(200)
end
@@ -332,7 +336,7 @@ describe API::API, api: true do
allow_any_instance_of(MergeRequest).
to receive(:can_be_merged?).and_return(false)
- put api("/projects/#{project.id}/merge_request/#{merge_request.id}/merge", user)
+ put api("/projects/#{project.id}/merge_requests/#{merge_request.id}/merge", user)
expect(response.status).to eq(406)
expect(json_response['message']).to eq('Branch cannot be merged')
@@ -340,14 +344,14 @@ describe API::API, api: true do
it "should return 405 if merge_request is not open" do
merge_request.close
- put api("/projects/#{project.id}/merge_request/#{merge_request.id}/merge", user)
+ put api("/projects/#{project.id}/merge_requests/#{merge_request.id}/merge", user)
expect(response.status).to eq(405)
expect(json_response['message']).to eq('405 Method Not Allowed')
end
it "should return 405 if merge_request is a work in progress" do
merge_request.update_attribute(:title, "WIP: #{merge_request.title}")
- put api("/projects/#{project.id}/merge_request/#{merge_request.id}/merge", user)
+ put api("/projects/#{project.id}/merge_requests/#{merge_request.id}/merge", user)
expect(response.status).to eq(405)
expect(json_response['message']).to eq('405 Method Not Allowed')
end
@@ -355,7 +359,7 @@ describe API::API, api: true do
it "should return 401 if user has no permissions to merge" do
user2 = create(:user)
project.team << [user2, :reporter]
- put api("/projects/#{project.id}/merge_request/#{merge_request.id}/merge", user2)
+ put api("/projects/#{project.id}/merge_requests/#{merge_request.id}/merge", user2)
expect(response.status).to eq(401)
expect(json_response['message']).to eq('401 Unauthorized')
end
@@ -364,7 +368,7 @@ describe API::API, api: true do
allow_any_instance_of(MergeRequest).to receive(:ci_commit).and_return(ci_commit)
allow(ci_commit).to receive(:active?).and_return(true)
- put api("/projects/#{project.id}/merge_request/#{merge_request.id}/merge", user), merge_when_build_succeeds: true
+ put api("/projects/#{project.id}/merge_requests/#{merge_request.id}/merge", user), merge_when_build_succeeds: true
expect(response.status).to eq(200)
expect(json_response['title']).to eq('Test')
@@ -372,33 +376,39 @@ describe API::API, api: true do
end
end
- describe "PUT /projects/:id/merge_request/:merge_request_id" do
- it "should return merge_request" do
- put api("/projects/#{project.id}/merge_request/#{merge_request.id}", user), title: "New title"
+ describe "PUT /projects/:id/merge_requests/:merge_request_id" do
+ it "updates title and returns merge_request" do
+ put api("/projects/#{project.id}/merge_requests/#{merge_request.id}", user), title: "New title"
expect(response.status).to eq(200)
expect(json_response['title']).to eq('New title')
end
- it "should return merge_request" do
- put api("/projects/#{project.id}/merge_request/#{merge_request.id}", user), description: "New description"
+ it "updates description and returns merge_request" do
+ put api("/projects/#{project.id}/merge_requests/#{merge_request.id}", user), description: "New description"
expect(response.status).to eq(200)
expect(json_response['description']).to eq('New description')
end
+ it "updates milestone_id and returns merge_request" do
+ put api("/projects/#{project.id}/merge_requests/#{merge_request.id}", user), milestone_id: milestone.id
+ expect(response.status).to eq(200)
+ expect(json_response['milestone']['id']).to eq(milestone.id)
+ end
+
it "should return 400 when source_branch is specified" do
- put api("/projects/#{project.id}/merge_request/#{merge_request.id}", user),
+ put api("/projects/#{project.id}/merge_requests/#{merge_request.id}", user),
source_branch: "master", target_branch: "master"
expect(response.status).to eq(400)
end
it "should return merge_request with renamed target_branch" do
- put api("/projects/#{project.id}/merge_request/#{merge_request.id}", user), target_branch: "wiki"
+ put api("/projects/#{project.id}/merge_requests/#{merge_request.id}", user), target_branch: "wiki"
expect(response.status).to eq(200)
expect(json_response['target_branch']).to eq('wiki')
end
it 'should return 400 on invalid label names' do
- put api("/projects/#{project.id}/merge_request/#{merge_request.id}",
+ put api("/projects/#{project.id}/merge_requests/#{merge_request.id}",
user),
title: 'new issue',
labels: 'label, ?'
@@ -407,11 +417,11 @@ describe API::API, api: true do
end
end
- describe "POST /projects/:id/merge_request/:merge_request_id/comments" do
+ describe "POST /projects/:id/merge_requests/:merge_request_id/comments" do
it "should return comment" do
original_count = merge_request.notes.size
- post api("/projects/#{project.id}/merge_request/#{merge_request.id}/comments", user), note: "My comment"
+ post api("/projects/#{project.id}/merge_requests/#{merge_request.id}/comments", user), note: "My comment"
expect(response.status).to eq(201)
expect(json_response['note']).to eq('My comment')
expect(json_response['author']['name']).to eq(user.name)
@@ -420,20 +430,20 @@ describe API::API, api: true do
end
it "should return 400 if note is missing" do
- post api("/projects/#{project.id}/merge_request/#{merge_request.id}/comments", user)
+ post api("/projects/#{project.id}/merge_requests/#{merge_request.id}/comments", user)
expect(response.status).to eq(400)
end
it "should return 404 if note is attached to non existent merge request" do
- post api("/projects/#{project.id}/merge_request/404/comments", user),
+ post api("/projects/#{project.id}/merge_requests/404/comments", user),
note: 'My comment'
expect(response.status).to eq(404)
end
end
- describe "GET :id/merge_request/:merge_request_id/comments" do
+ describe "GET :id/merge_requests/:merge_request_id/comments" do
it "should return merge_request comments ordered by created_at" do
- get api("/projects/#{project.id}/merge_request/#{merge_request.id}/comments", user)
+ get api("/projects/#{project.id}/merge_requests/#{merge_request.id}/comments", user)
expect(response.status).to eq(200)
expect(json_response).to be_an Array
expect(json_response.length).to eq(2)
@@ -443,11 +453,33 @@ describe API::API, api: true do
end
it "should return a 404 error if merge_request_id not found" do
- get api("/projects/#{project.id}/merge_request/999/comments", user)
+ get api("/projects/#{project.id}/merge_requests/999/comments", user)
expect(response.status).to eq(404)
end
end
+ describe 'GET :id/merge_requests/:merge_request_id/closes_issues' do
+ it 'returns the issue that will be closed on merge' do
+ issue = create(:issue, project: project)
+ mr = merge_request.tap do |mr|
+ mr.update_attribute(:description, "Closes #{issue.to_reference(mr.project)}")
+ end
+
+ get api("/projects/#{project.id}/merge_requests/#{mr.id}/closes_issues", user)
+ expect(response.status).to eq(200)
+ expect(json_response).to be_an Array
+ expect(json_response.length).to eq(1)
+ expect(json_response.first['id']).to eq(issue.id)
+ end
+
+ it 'returns an empty array when there are no issues to be closed' do
+ get api("/projects/#{project.id}/merge_requests/#{merge_request.id}/closes_issues", user)
+ expect(response.status).to eq(200)
+ expect(json_response).to be_an Array
+ expect(json_response.length).to eq(0)
+ end
+ end
+
def mr_with_later_created_and_updated_at_time
merge_request
merge_request.created_at += 1.hour
diff --git a/spec/requests/api/projects_spec.rb b/spec/requests/api/projects_spec.rb
index 6f4c336b66..2a310f3834 100644
--- a/spec/requests/api/projects_spec.rb
+++ b/spec/requests/api/projects_spec.rb
@@ -90,6 +90,29 @@ describe API::API, api: true do
end
end
+ context 'and using the visibility filter' do
+ it 'should filter based on private visibility param' do
+ get api('/projects', user), { visibility: 'private' }
+ expect(response.status).to eq(200)
+ expect(json_response).to be_an Array
+ expect(json_response.length).to eq(user.namespace.projects.where(visibility_level: Gitlab::VisibilityLevel::PRIVATE).count)
+ end
+
+ it 'should filter based on internal visibility param' do
+ get api('/projects', user), { visibility: 'internal' }
+ expect(response.status).to eq(200)
+ expect(json_response).to be_an Array
+ expect(json_response.length).to eq(user.namespace.projects.where(visibility_level: Gitlab::VisibilityLevel::INTERNAL).count)
+ end
+
+ it 'should filter based on public visibility param' do
+ get api('/projects', user), { visibility: 'public' }
+ expect(response.status).to eq(200)
+ expect(json_response).to be_an Array
+ expect(json_response.length).to eq(user.namespace.projects.where(visibility_level: Gitlab::VisibilityLevel::PUBLIC).count)
+ end
+ end
+
context 'and using sorting' do
before do
project2
diff --git a/spec/requests/api/runners_spec.rb b/spec/requests/api/runners_spec.rb
new file mode 100644
index 0000000000..78484747d6
--- /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, user: user, project: project, access_level: ProjectMember::MASTER)
+ create(:project_member, user: user, project: project2, access_level: ProjectMember::MASTER)
+ create(:project_member, user: user2, project: project, access_level: ProjectMember::REPORTER)
+ 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/ci/api/builds_spec.rb b/spec/requests/ci/api/builds_spec.rb
index 244947762d..57d7eb927f 100644
--- a/spec/requests/ci/api/builds_spec.rb
+++ b/spec/requests/ci/api/builds_spec.rb
@@ -131,28 +131,36 @@ 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
context "Artifacts" do
let(:file_upload) { fixture_file_upload(Rails.root + 'spec/fixtures/banana_sample.gif', 'image/gif') }
let(:file_upload2) { fixture_file_upload(Rails.root + 'spec/fixtures/dk.png', 'image/gif') }
- 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, commit: commit, runner_id: runner.id) }
let(:authorize_url) { ci_api("/builds/#{build.id}/artifacts/authorize") }
let(:post_url) { ci_api("/builds/#{build.id}/artifacts") }
let(:delete_url) { ci_api("/builds/#{build.id}/artifacts") }
@@ -160,12 +168,10 @@ describe Ci::API::API do
let(:headers) { { "GitLab-Workhorse" => "1.0" } }
let(:headers_with_token) { headers.merge(Ci::API::Helpers::BUILD_TOKEN_HEADER => build.token) }
+ before { build.run! }
+
describe "POST /builds/:id/artifacts/authorize" do
context "should authorize posting artifact to running build" do
- before do
- build.run!
- end
-
it "using token as parameter" do
post authorize_url, { token: build.token }, headers
expect(response.status).to eq(200)
@@ -180,10 +186,6 @@ describe Ci::API::API do
end
context "should fail to post too large artifact" do
- before do
- build.run!
- end
-
it "using token as parameter" do
stub_application_setting(max_artifacts_size: 0)
post authorize_url, { token: build.token, filesize: 100 }, headers
@@ -197,26 +199,32 @@ describe Ci::API::API do
end
end
- context "should get denied" do
- it 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
end
describe "POST /builds/:id/artifacts" do
- context "Disable sanitizer" do
+ context "disable sanitizer" do
before do
# by configuring this path we allow to pass temp file from any path
allow(ArtifactUploader).to receive(:artifacts_upload_path).and_return('/')
end
- context "should post artifact to running build" do
- before do
- build.run!
- 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)
expect(response.status).to eq(201)
@@ -245,7 +253,6 @@ describe Ci::API::API do
let(:stored_metadata_file) { build.reload.artifacts_metadata.file }
before do
- build.run!
post(post_url, post_data, headers_with_token)
end
@@ -257,11 +264,8 @@ describe Ci::API::API do
'metadata.name' => metadata.original_filename }
end
- it 'responds with valid status' do
- expect(response.status).to eq(201)
- end
-
it 'stores artifacts and artifacts metadata' do
+ expect(response.status).to eq(201)
expect(stored_artifacts_file.original_filename).to eq(artifacts.original_filename)
expect(stored_metadata_file.original_filename).to eq(metadata.original_filename)
end
@@ -282,56 +286,42 @@ describe Ci::API::API do
end
end
-
- context "should fail to post too large artifact" do
- before do
- build.run!
- end
-
- it do
+ context "artifacts file is too large" do
+ it "should fail to post too large artifact" do
stub_application_setting(max_artifacts_size: 0)
upload_artifacts(file_upload, headers_with_token)
expect(response.status).to eq(413)
end
end
- context "should fail to post artifacts without file" do
- before do
- build.run!
- end
-
- it do
+ context "artifacts post request does not contain file" do
+ it "should fail to post artifacts without file" do
post post_url, {}, headers_with_token
expect(response.status).to eq(400)
end
end
- context "should fail to post artifacts without GitLab-Workhorse" do
- before do
- build.run!
- end
-
- it do
+ context 'GitLab Workhorse is not configured' do
+ it "should fail to post artifacts without GitLab-Workhorse" do
post post_url, { token: build.token }, {}
expect(response.status).to eq(403)
end
end
end
- context "should fail to post artifacts for outside of tmp path" do
+ context "artifacts are being stored outside of tmp path" do
before do
# by configuring this path we allow to pass file from @tmpdir only
# but all temporary files are stored in system tmp directory
@tmpdir = Dir.mktmpdir
allow(ArtifactUploader).to receive(:artifacts_upload_path).and_return(@tmpdir)
- build.run!
end
after do
FileUtils.remove_entry @tmpdir
end
- it do
+ it "should fail to post artifacts for outside of tmp path" do
upload_artifacts(file_upload, headers_with_token)
expect(response.status).to eq(400)
end
@@ -349,33 +339,37 @@ describe Ci::API::API do
end
end
- describe "DELETE /builds/:id/artifacts" do
- before do
- build.run!
- post delete_url, token: build.token, file: file_upload
- end
+ describe 'DELETE /builds/:id/artifacts' do
+ let(:build) { create(:ci_build, :artifacts) }
+ before { delete delete_url, token: build.token }
- it "should delete artifact build" do
- build.success
- delete delete_url, token: build.token
+ it 'should remove build artifacts' do
expect(response.status).to eq(200)
+ expect(build.artifacts_file.exists?).to be_falsy
+ expect(build.artifacts_metadata.exists?).to be_falsy
end
end
- describe "GET /builds/:id/artifacts" do
- before do
- build.run!
+ describe 'GET /builds/:id/artifacts' do
+ before { get get_url, token: build.token }
+
+ context 'build has artifacts' do
+ let(:build) { create(:ci_build, :artifacts) }
+ let(:download_headers) do
+ { 'Content-Transfer-Encoding'=>'binary',
+ 'Content-Disposition'=>'attachment; filename=ci_build_artifacts.zip' }
+ end
+
+ it 'should download artifact' do
+ expect(response.status).to eq(200)
+ expect(response.headers).to include download_headers
+ end
end
- it "should download artifact" do
- build.update_attributes(artifacts_file: file_upload)
- get get_url, token: build.token
- expect(response.status).to eq(200)
- end
-
- it "should fail to download if no artifact uploaded" do
- get get_url, token: build.token
- expect(response.status).to eq(404)
+ context 'build does not has artifacts' do
+ it 'should respond with not found' do
+ expect(response.status).to eq(404)
+ end
end
end
end
diff --git a/spec/routing/project_routing_spec.rb b/spec/routing/project_routing_spec.rb
index 22ba25217f..538f44e4f3 100644
--- a/spec/routing/project_routing_spec.rb
+++ b/spec/routing/project_routing_spec.rb
@@ -321,12 +321,12 @@ describe Projects::HooksController, 'routing' do
end
end
-# project_commit GET /:project_id/commit/:id(.:format) commit#show {id: /[[:alnum:]]{6,40}/, project_id: /[^\/]+/}
+# project_commit GET /:project_id/commit/:id(.:format) commit#show {id: /\h{7,40}/, project_id: /[^\/]+/}
describe Projects::CommitController, 'routing' do
it 'to #show' do
- expect(get('/gitlab/gitlabhq/commit/4246fb')).to route_to('projects/commit#show', namespace_id: 'gitlab', project_id: 'gitlabhq', id: '4246fb')
- expect(get('/gitlab/gitlabhq/commit/4246fb.diff')).to route_to('projects/commit#show', namespace_id: 'gitlab', project_id: 'gitlabhq', id: '4246fb', format: 'diff')
- expect(get('/gitlab/gitlabhq/commit/4246fb.patch')).to route_to('projects/commit#show', namespace_id: 'gitlab', project_id: 'gitlabhq', id: '4246fb', format: 'patch')
+ expect(get('/gitlab/gitlabhq/commit/4246fbd')).to route_to('projects/commit#show', namespace_id: 'gitlab', project_id: 'gitlabhq', id: '4246fbd')
+ expect(get('/gitlab/gitlabhq/commit/4246fbd.diff')).to route_to('projects/commit#show', namespace_id: 'gitlab', project_id: 'gitlabhq', id: '4246fbd', format: 'diff')
+ expect(get('/gitlab/gitlabhq/commit/4246fbd.patch')).to route_to('projects/commit#show', namespace_id: 'gitlab', project_id: 'gitlabhq', id: '4246fbd', format: 'patch')
expect(get('/gitlab/gitlabhq/commit/4246fbd13872934f72a8fd0d6fb1317b47b59cb5')).to route_to('projects/commit#show', namespace_id: 'gitlab', project_id: 'gitlabhq', id: '4246fbd13872934f72a8fd0d6fb1317b47b59cb5')
end
end
@@ -496,11 +496,11 @@ end
describe Projects::ForksController, 'routing' do
it 'to #new' do
- expect(get('/gitlab/gitlabhq/fork/new')).to route_to('projects/forks#new', namespace_id: 'gitlab', project_id: 'gitlabhq')
+ expect(get('/gitlab/gitlabhq/forks/new')).to route_to('projects/forks#new', namespace_id: 'gitlab', project_id: 'gitlabhq')
end
it 'to #create' do
- expect(post('/gitlab/gitlabhq/fork')).to route_to('projects/forks#create', namespace_id: 'gitlab', project_id: 'gitlabhq')
+ expect(post('/gitlab/gitlabhq/forks')).to route_to('projects/forks#create', namespace_id: 'gitlab', project_id: 'gitlabhq')
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 0000000000..1fca362868
--- /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 c1080ef190..994585fb32 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,34 +14,67 @@ 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 }
+
+ it 'flushes general cached data' do
+ expect(project.repository).to receive(:expire_cache).with('master')
+
+ subject
+ end
+
+ it 'flushes the visible content cache' do
+ expect(project.repository).to receive(:expire_has_visible_content_cache)
+
+ subject
+ end
end
context 'existing branch' do
- subject do
- service.execute(project, user, @oldrev, @newrev, @ref)
- end
it { is_expected.to be_truthy }
+
+ it 'flushes general cached data' do
+ expect(project.repository).to receive(:expire_cache).with('master')
+
+ subject
+ end
end
context 'rm branch' do
- subject do
- service.execute(project, user, @oldrev, @blankrev, @ref)
- end
+
+ let(:newrev) { @blankrev }
it { is_expected.to be_truthy }
+
+ it 'flushes the visible content cache' do
+ expect(project.repository).to receive(:expire_has_visible_content_cache)
+
+ subject
+ end
+
+ it 'flushes general cached data' do
+ expect(project.repository).to receive(:expire_cache).with('master')
+
+ subject
+ end
end
end
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
@@ -104,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
@@ -128,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
@@ -137,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
@@ -146,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
@@ -174,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
@@ -182,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
@@ -192,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
@@ -201,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
@@ -225,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
@@ -244,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
@@ -257,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
@@ -298,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/
@@ -316,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
@@ -327,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
@@ -346,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 3a8daf28f5..62b25709a5 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 2148d091a5..5e7915db7e 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 87da0e9618..e579e49dfa 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 50d0c28879..8443a00e70 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 be8f1676ee..120f4d6a66 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 de9fed2b7d..f285517cda 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,68 @@ 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)
+ expect(MergeWorker).to receive(:perform_async)
+ service.trigger(build)
+ end
+ end
+
+ context 'commit status without ref' do
+ let(:commit_status) { create(:generic_commit_status, status: 'success') }
+
+ 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
+
+ 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/update_service_spec.rb b/spec/services/merge_requests/update_service_spec.rb
index 2e9e6e0870..99703c7a8e 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/create_service_spec.rb b/spec/services/notes/create_service_spec.rb
index a797a2fe4a..ff23f13e1c 100644
--- a/spec/services/notes/create_service_spec.rb
+++ b/spec/services/notes/create_service_spec.rb
@@ -14,9 +14,7 @@ describe Notes::CreateService, services: true do
noteable_type: 'Issue',
noteable_id: issue.id
}
-
- expect(project).to receive(:execute_hooks)
- expect(project).to receive(:execute_services)
+
@note = Notes::CreateService.new(project, user, opts).execute
end
diff --git a/spec/services/notes/post_process_service_spec.rb b/spec/services/notes/post_process_service_spec.rb
new file mode 100644
index 0000000000..d4c50f824c
--- /dev/null
+++ b/spec/services/notes/post_process_service_spec.rb
@@ -0,0 +1,27 @@
+require 'spec_helper'
+
+describe Notes::PostProcessService, services: true do
+ let(:project) { create(:empty_project) }
+ let(:issue) { create(:issue, project: project) }
+ let(:user) { create(:user) }
+
+ describe :execute do
+ before do
+ project.team << [user, :master]
+ note_opts = {
+ note: 'Awesome comment',
+ noteable_type: 'Issue',
+ noteable_id: issue.id
+ }
+
+ @note = Notes::CreateService.new(project, user, note_opts).execute
+ end
+
+ it do
+ expect(project).to receive(:execute_hooks)
+ expect(project).to receive(:execute_services)
+
+ Notes::PostProcessService.new(@note).execute
+ end
+ 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 0000000000..dde4bde7dc
--- /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/projects/import_service_spec.rb b/spec/services/projects/import_service_spec.rb
new file mode 100644
index 0000000000..04f474c736
--- /dev/null
+++ b/spec/services/projects/import_service_spec.rb
@@ -0,0 +1,106 @@
+require 'spec_helper'
+
+describe Projects::ImportService, services: true do
+ let!(:project) { create(:empty_project) }
+ let(:user) { project.creator }
+
+ subject { described_class.new(project, user) }
+
+ describe '#execute' do
+ context 'with unknown url' do
+ before do
+ project.import_url = Project::UNKNOWN_IMPORT_URL
+ end
+
+ it 'succeeds if repository is created successfully' do
+ expect(project).to receive(:create_repository).and_return(true)
+
+ result = subject.execute
+
+ expect(result[:status]).to eq :success
+ end
+
+ it 'fails if repository creation fails' do
+ expect(project).to receive(:create_repository).and_return(false)
+
+ result = subject.execute
+
+ expect(result[:status]).to eq :error
+ expect(result[:message]).to eq 'The repository could not be created.'
+ end
+ end
+
+ context 'with known url' do
+ before do
+ project.import_url = 'https://github.com/vim/vim.git'
+ end
+
+ it 'succeeds if repository import is successfully' do
+ expect_any_instance_of(Gitlab::Shell).to receive(:import_repository).with(project.path_with_namespace, project.import_url).and_return(true)
+
+ result = subject.execute
+
+ expect(result[:status]).to eq :success
+ end
+
+ it 'fails if repository import fails' do
+ expect_any_instance_of(Gitlab::Shell).to receive(:import_repository).with(project.path_with_namespace, project.import_url).and_raise(Gitlab::Shell::Error.new('Failed to import the repository'))
+
+ result = subject.execute
+
+ expect(result[:status]).to eq :error
+ expect(result[:message]).to eq 'Failed to import the repository'
+ end
+ end
+
+ context 'with valid importer' do
+ before do
+ stub_github_omniauth_provider
+
+ project.import_url = 'https://github.com/vim/vim.git'
+ project.import_type = 'github'
+
+ allow(project).to receive(:import_data).and_return(double.as_null_object)
+ end
+
+ it 'succeeds if importer succeeds' do
+ expect_any_instance_of(Gitlab::Shell).to receive(:import_repository).with(project.path_with_namespace, project.import_url).and_return(true)
+ expect_any_instance_of(Gitlab::GithubImport::Importer).to receive(:execute).and_return(true)
+
+ result = subject.execute
+
+ expect(result[:status]).to eq :success
+ end
+
+ it 'fails if importer fails' do
+ expect_any_instance_of(Gitlab::Shell).to receive(:import_repository).with(project.path_with_namespace, project.import_url).and_return(true)
+ expect_any_instance_of(Gitlab::GithubImport::Importer).to receive(:execute).and_return(false)
+
+ result = subject.execute
+
+ expect(result[:status]).to eq :error
+ expect(result[:message]).to eq 'The remote data could not be imported.'
+ end
+
+ it 'fails if importer raise an error' do
+ expect_any_instance_of(Gitlab::Shell).to receive(:import_repository).with(project.path_with_namespace, project.import_url).and_return(true)
+ expect_any_instance_of(Gitlab::GithubImport::Importer).to receive(:execute).and_raise(Projects::ImportService::Error.new('Github: failed to connect API'))
+
+ result = subject.execute
+
+ expect(result[:status]).to eq :error
+ expect(result[:message]).to eq 'Github: failed to connect API'
+ end
+ end
+
+ def stub_github_omniauth_provider
+ provider = OpenStruct.new(
+ name: 'github',
+ app_id: 'asd123',
+ app_secret: 'asd123'
+ )
+
+ Gitlab.config.omniauth.providers << provider
+ end
+ end
+end
diff --git a/spec/services/system_note_service_spec.rb b/spec/services/system_note_service_spec.rb
index d3364a7102..1bdc03af12 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
diff --git a/spec/services/todo_service_spec.rb b/spec/services/todo_service_spec.rb
new file mode 100644
index 0000000000..96420acb31
--- /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 fed1ab6ee3..65d59e6813 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
@@ -19,3 +19,9 @@ unless ENV['CI'] || ENV['CI_SERVER']
# Keep only the screenshots generated from the last failing test suite
Capybara::Screenshot.prune_strategy = :keep_last_run
end
+
+RSpec.configure do |config|
+ config.before(:suite) do
+ TestEnv.warm_asset_cache
+ end
+end
diff --git a/spec/support/email_format_shared_examples.rb b/spec/support/email_format_shared_examples.rb
new file mode 100644
index 0000000000..b924a208e7
--- /dev/null
+++ b/spec/support/email_format_shared_examples.rb
@@ -0,0 +1,44 @@
+# Specifications for behavior common to all objects with an email attribute.
+# Takes a list of email-format attributes and requires:
+# - subject { "the object with a attribute= setter" }
+# Note: You have access to `email_value` which is the email address value
+# being currently tested).
+
+shared_examples 'an object with email-formated attributes' do |*attributes|
+ attributes.each do |attribute|
+ describe "specifically its :#{attribute} attribute" do
+ %w[
+ info@example.com
+ info+test@example.com
+ o'reilly@example.com
+ mailto:test@example.com
+ lol!'+=?><#$%^&*()@gmail.com
+ ].each do |valid_email|
+ context "with a value of '#{valid_email}'" do
+ let(:email_value) { valid_email }
+
+ it 'is valid' do
+ subject.send("#{attribute}=", valid_email)
+
+ expect(subject).to be_valid
+ end
+ end
+ end
+
+ %w[
+ foobar
+ test@test@example.com
+ ].each do |invalid_email|
+ context "with a value of '#{invalid_email}'" do
+ let(:email_value) { invalid_email }
+
+ it 'is invalid' do
+ subject.send("#{attribute}=", invalid_email)
+
+ expect(subject).to be_invalid
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/spec/support/filter_spec_helper.rb b/spec/support/filter_spec_helper.rb
index d6e03cbef3..ef5ea7d626 100644
--- a/spec/support/filter_spec_helper.rb
+++ b/spec/support/filter_spec_helper.rb
@@ -67,9 +67,9 @@ module FilterSpecHelper
if reference =~ /\A(.+)?.\d+\z/
# Integer-based reference with optional project prefix
reference.gsub(/\d+\z/) { |i| i.to_i + 1 }
- elsif reference =~ /\A(.+@)?(\h{6,40}\z)/
+ elsif reference =~ /\A(.+@)?(\h{7,40}\z)/
# SHA-based reference with optional prefix
- reference.gsub(/\h{6,40}\z/) { |v| v.reverse }
+ reference.gsub(/\h{7,40}\z/) { |v| v.reverse }
else
reference.gsub(/\w+\z/) { |v| v.reverse }
end
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 0000000000..422083875d
--- /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/test_env.rb b/spec/support/test_env.rb
index 4f4743bff6..0d1bd030f3 100644
--- a/spec/support/test_env.rb
+++ b/spec/support/test_env.rb
@@ -146,6 +146,22 @@ module TestEnv
FileUtils.chmod_R 0755, target_repo_path
end
+ # When no cached assets exist, manually hit the root path to create them
+ #
+ # Otherwise they'd be created by the first test, often timing out and
+ # causing a transient test failure
+ def warm_asset_cache
+ return if warm_asset_cache?
+ return unless defined?(Capybara)
+
+ Capybara.current_session.driver.visit '/'
+ end
+
+ def warm_asset_cache?
+ cache = Rails.root.join(*%w(tmp cache assets test))
+ Dir.exist?(cache) && Dir.entries(cache).length > 2
+ end
+
private
def factory_repo_path
@@ -172,7 +188,6 @@ module TestEnv
'gitlab-test-fork'
end
-
# Prevent developer git configurations from being persisted to test
# repositories
def git_env
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 0000000000..05a76ee4bd
--- /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 dae3199262..172537474e 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 0000000000..6739063543
--- /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/Chart.js b/vendor/assets/javascripts/Chart.js
new file mode 100755
index 0000000000..c264262ba7
--- /dev/null
+++ b/vendor/assets/javascripts/Chart.js
@@ -0,0 +1,3477 @@
+/*!
+ * Chart.js
+ * http://chartjs.org/
+ * Version: 1.0.2
+ *
+ * Copyright 2015 Nick Downie
+ * Released under the MIT license
+ * https://github.com/nnnick/Chart.js/blob/master/LICENSE.md
+ */
+
+
+(function(){
+
+ "use strict";
+
+ //Declare root variable - window in the browser, global on the server
+ var root = this,
+ previous = root.Chart;
+
+ //Occupy the global variable of Chart, and create a simple base class
+ var Chart = function(context){
+ var chart = this;
+ this.canvas = context.canvas;
+
+ this.ctx = context;
+
+ //Variables global to the chart
+ var computeDimension = function(element,dimension)
+ {
+ if (element['offset'+dimension])
+ {
+ return element['offset'+dimension];
+ }
+ else
+ {
+ return document.defaultView.getComputedStyle(element).getPropertyValue(dimension);
+ }
+ }
+
+ var width = this.width = computeDimension(context.canvas,'Width');
+ var height = this.height = computeDimension(context.canvas,'Height');
+
+ // Firefox requires this to work correctly
+ context.canvas.width = width;
+ context.canvas.height = height;
+
+ var width = this.width = context.canvas.width;
+ var height = this.height = context.canvas.height;
+ this.aspectRatio = this.width / this.height;
+ //High pixel density displays - multiply the size of the canvas height/width by the device pixel ratio, then scale.
+ helpers.retinaScale(this);
+
+ return this;
+ };
+ //Globally expose the defaults to allow for user updating/changing
+ Chart.defaults = {
+ global: {
+ // Boolean - Whether to animate the chart
+ animation: true,
+
+ // Number - Number of animation steps
+ animationSteps: 60,
+
+ // String - Animation easing effect
+ animationEasing: "easeOutQuart",
+
+ // Boolean - If we should show the scale at all
+ showScale: true,
+
+ // Boolean - If we want to override with a hard coded scale
+ scaleOverride: false,
+
+ // ** Required if scaleOverride is true **
+ // Number - The number of steps in a hard coded scale
+ scaleSteps: null,
+ // Number - The value jump in the hard coded scale
+ scaleStepWidth: null,
+ // Number - The scale starting value
+ scaleStartValue: null,
+
+ // String - Colour of the scale line
+ scaleLineColor: "rgba(0,0,0,.1)",
+
+ // Number - Pixel width of the scale line
+ scaleLineWidth: 1,
+
+ // Boolean - Whether to show labels on the scale
+ scaleShowLabels: true,
+
+ // Interpolated JS string - can access value
+ scaleLabel: "<%=value%>",
+
+ // Boolean - Whether the scale should stick to integers, and not show any floats even if drawing space is there
+ scaleIntegersOnly: true,
+
+ // Boolean - Whether the scale should start at zero, or an order of magnitude down from the lowest value
+ scaleBeginAtZero: false,
+
+ // String - Scale label font declaration for the scale label
+ scaleFontFamily: "'Helvetica Neue', 'Helvetica', 'Arial', sans-serif",
+
+ // Number - Scale label font size in pixels
+ scaleFontSize: 12,
+
+ // String - Scale label font weight style
+ scaleFontStyle: "normal",
+
+ // String - Scale label font colour
+ scaleFontColor: "#666",
+
+ // Boolean - whether or not the chart should be responsive and resize when the browser does.
+ responsive: false,
+
+ // Boolean - whether to maintain the starting aspect ratio or not when responsive, if set to false, will take up entire container
+ maintainAspectRatio: true,
+
+ // Boolean - Determines whether to draw tooltips on the canvas or not - attaches events to touchmove & mousemove
+ showTooltips: true,
+
+ // Boolean - Determines whether to draw built-in tooltip or call custom tooltip function
+ customTooltips: false,
+
+ // Array - Array of string names to attach tooltip events
+ tooltipEvents: ["mousemove", "touchstart", "touchmove", "mouseout"],
+
+ // String - Tooltip background colour
+ tooltipFillColor: "rgba(0,0,0,0.8)",
+
+ // String - Tooltip label font declaration for the scale label
+ tooltipFontFamily: "'Helvetica Neue', 'Helvetica', 'Arial', sans-serif",
+
+ // Number - Tooltip label font size in pixels
+ tooltipFontSize: 14,
+
+ // String - Tooltip font weight style
+ tooltipFontStyle: "normal",
+
+ // String - Tooltip label font colour
+ tooltipFontColor: "#fff",
+
+ // String - Tooltip title font declaration for the scale label
+ tooltipTitleFontFamily: "'Helvetica Neue', 'Helvetica', 'Arial', sans-serif",
+
+ // Number - Tooltip title font size in pixels
+ tooltipTitleFontSize: 14,
+
+ // String - Tooltip title font weight style
+ tooltipTitleFontStyle: "bold",
+
+ // String - Tooltip title font colour
+ tooltipTitleFontColor: "#fff",
+
+ // Number - pixel width of padding around tooltip text
+ tooltipYPadding: 6,
+
+ // Number - pixel width of padding around tooltip text
+ tooltipXPadding: 6,
+
+ // Number - Size of the caret on the tooltip
+ tooltipCaretSize: 8,
+
+ // Number - Pixel radius of the tooltip border
+ tooltipCornerRadius: 6,
+
+ // Number - Pixel offset from point x to tooltip edge
+ tooltipXOffset: 10,
+
+ // String - Template string for single tooltips
+ tooltipTemplate: "<%if (label){%><%=label%>: <%}%><%= value %>",
+
+ // String - Template string for single tooltips
+ multiTooltipTemplate: "<%= value %>",
+
+ // String - Colour behind the legend colour block
+ multiTooltipKeyBackground: '#fff',
+
+ // Function - Will fire on animation progression.
+ onAnimationProgress: function(){},
+
+ // Function - Will fire on animation completion.
+ onAnimationComplete: function(){}
+
+ }
+ };
+
+ //Create a dictionary of chart types, to allow for extension of existing types
+ Chart.types = {};
+
+ //Global Chart helpers object for utility methods and classes
+ var helpers = Chart.helpers = {};
+
+ //-- Basic js utility methods
+ var each = helpers.each = function(loopable,callback,self){
+ var additionalArgs = Array.prototype.slice.call(arguments, 3);
+ // Check to see if null or undefined firstly.
+ if (loopable){
+ if (loopable.length === +loopable.length){
+ var i;
+ for (i=0; i= 0; i--) {
+ var currentItem = arrayToSearch[i];
+ if (filterCallback(currentItem)){
+ return currentItem;
+ }
+ }
+ },
+ inherits = helpers.inherits = function(extensions){
+ //Basic javascript inheritance based on the model created in Backbone.js
+ var parent = this;
+ var ChartElement = (extensions && extensions.hasOwnProperty("constructor")) ? extensions.constructor : function(){ return parent.apply(this, arguments); };
+
+ var Surrogate = function(){ this.constructor = ChartElement;};
+ Surrogate.prototype = parent.prototype;
+ ChartElement.prototype = new Surrogate();
+
+ ChartElement.extend = inherits;
+
+ if (extensions) extend(ChartElement.prototype, extensions);
+
+ ChartElement.__super__ = parent.prototype;
+
+ return ChartElement;
+ },
+ noop = helpers.noop = function(){},
+ uid = helpers.uid = (function(){
+ var id=0;
+ return function(){
+ return "chart-" + id++;
+ };
+ })(),
+ warn = helpers.warn = function(str){
+ //Method for warning of errors
+ if (window.console && typeof window.console.warn == "function") console.warn(str);
+ },
+ amd = helpers.amd = (typeof define == 'function' && define.amd),
+ //-- Math methods
+ isNumber = helpers.isNumber = function(n){
+ return !isNaN(parseFloat(n)) && isFinite(n);
+ },
+ max = helpers.max = function(array){
+ return Math.max.apply( Math, array );
+ },
+ min = helpers.min = function(array){
+ return Math.min.apply( Math, array );
+ },
+ cap = helpers.cap = function(valueToCap,maxValue,minValue){
+ if(isNumber(maxValue)) {
+ if( valueToCap > maxValue ) {
+ return maxValue;
+ }
+ }
+ else if(isNumber(minValue)){
+ if ( valueToCap < minValue ){
+ return minValue;
+ }
+ }
+ return valueToCap;
+ },
+ getDecimalPlaces = helpers.getDecimalPlaces = function(num){
+ if (num%1!==0 && isNumber(num)){
+ return num.toString().split(".")[1].length;
+ }
+ else {
+ return 0;
+ }
+ },
+ toRadians = helpers.radians = function(degrees){
+ return degrees * (Math.PI/180);
+ },
+ // Gets the angle from vertical upright to the point about a centre.
+ getAngleFromPoint = helpers.getAngleFromPoint = function(centrePoint, anglePoint){
+ var distanceFromXCenter = anglePoint.x - centrePoint.x,
+ distanceFromYCenter = anglePoint.y - centrePoint.y,
+ radialDistanceFromCenter = Math.sqrt( distanceFromXCenter * distanceFromXCenter + distanceFromYCenter * distanceFromYCenter);
+
+
+ var angle = Math.PI * 2 + Math.atan2(distanceFromYCenter, distanceFromXCenter);
+
+ //If the segment is in the top left quadrant, we need to add another rotation to the angle
+ if (distanceFromXCenter < 0 && distanceFromYCenter < 0){
+ angle += Math.PI*2;
+ }
+
+ return {
+ angle: angle,
+ distance: radialDistanceFromCenter
+ };
+ },
+ aliasPixel = helpers.aliasPixel = function(pixelWidth){
+ return (pixelWidth % 2 === 0) ? 0 : 0.5;
+ },
+ splineCurve = helpers.splineCurve = function(FirstPoint,MiddlePoint,AfterPoint,t){
+ //Props to Rob Spencer at scaled innovation for his post on splining between points
+ //http://scaledinnovation.com/analytics/splines/aboutSplines.html
+ var d01=Math.sqrt(Math.pow(MiddlePoint.x-FirstPoint.x,2)+Math.pow(MiddlePoint.y-FirstPoint.y,2)),
+ d12=Math.sqrt(Math.pow(AfterPoint.x-MiddlePoint.x,2)+Math.pow(AfterPoint.y-MiddlePoint.y,2)),
+ fa=t*d01/(d01+d12),// scaling factor for triangle Ta
+ fb=t*d12/(d01+d12);
+ return {
+ inner : {
+ x : MiddlePoint.x-fa*(AfterPoint.x-FirstPoint.x),
+ y : MiddlePoint.y-fa*(AfterPoint.y-FirstPoint.y)
+ },
+ outer : {
+ x: MiddlePoint.x+fb*(AfterPoint.x-FirstPoint.x),
+ y : MiddlePoint.y+fb*(AfterPoint.y-FirstPoint.y)
+ }
+ };
+ },
+ calculateOrderOfMagnitude = helpers.calculateOrderOfMagnitude = function(val){
+ return Math.floor(Math.log(val) / Math.LN10);
+ },
+ calculateScaleRange = helpers.calculateScaleRange = function(valuesArray, drawingSize, textSize, startFromZero, integersOnly){
+
+ //Set a minimum step of two - a point at the top of the graph, and a point at the base
+ var minSteps = 2,
+ maxSteps = Math.floor(drawingSize/(textSize * 1.5)),
+ skipFitting = (minSteps >= maxSteps);
+
+ var maxValue = max(valuesArray),
+ minValue = min(valuesArray);
+
+ // We need some degree of seperation here to calculate the scales if all the values are the same
+ // Adding/minusing 0.5 will give us a range of 1.
+ if (maxValue === minValue){
+ maxValue += 0.5;
+ // So we don't end up with a graph with a negative start value if we've said always start from zero
+ if (minValue >= 0.5 && !startFromZero){
+ minValue -= 0.5;
+ }
+ else{
+ // Make up a whole number above the values
+ maxValue += 0.5;
+ }
+ }
+
+ var valueRange = Math.abs(maxValue - minValue),
+ rangeOrderOfMagnitude = calculateOrderOfMagnitude(valueRange),
+ graphMax = Math.ceil(maxValue / (1 * Math.pow(10, rangeOrderOfMagnitude))) * Math.pow(10, rangeOrderOfMagnitude),
+ graphMin = (startFromZero) ? 0 : Math.floor(minValue / (1 * Math.pow(10, rangeOrderOfMagnitude))) * Math.pow(10, rangeOrderOfMagnitude),
+ graphRange = graphMax - graphMin,
+ stepValue = Math.pow(10, rangeOrderOfMagnitude),
+ numberOfSteps = Math.round(graphRange / stepValue);
+
+ //If we have more space on the graph we'll use it to give more definition to the data
+ while((numberOfSteps > maxSteps || (numberOfSteps * 2) < maxSteps) && !skipFitting) {
+ if(numberOfSteps > maxSteps){
+ stepValue *=2;
+ numberOfSteps = Math.round(graphRange/stepValue);
+ // Don't ever deal with a decimal number of steps - cancel fitting and just use the minimum number of steps.
+ if (numberOfSteps % 1 !== 0){
+ skipFitting = true;
+ }
+ }
+ //We can fit in double the amount of scale points on the scale
+ else{
+ //If user has declared ints only, and the step value isn't a decimal
+ if (integersOnly && rangeOrderOfMagnitude >= 0){
+ //If the user has said integers only, we need to check that making the scale more granular wouldn't make it a float
+ if(stepValue/2 % 1 === 0){
+ stepValue /=2;
+ numberOfSteps = Math.round(graphRange/stepValue);
+ }
+ //If it would make it a float break out of the loop
+ else{
+ break;
+ }
+ }
+ //If the scale doesn't have to be an int, make the scale more granular anyway.
+ else{
+ stepValue /=2;
+ numberOfSteps = Math.round(graphRange/stepValue);
+ }
+
+ }
+ }
+
+ if (skipFitting){
+ numberOfSteps = minSteps;
+ stepValue = graphRange / numberOfSteps;
+ }
+
+ return {
+ steps : numberOfSteps,
+ stepValue : stepValue,
+ min : graphMin,
+ max : graphMin + (numberOfSteps * stepValue)
+ };
+
+ },
+ /* jshint ignore:start */
+ // Blows up jshint errors based on the new Function constructor
+ //Templating methods
+ //Javascript micro templating by John Resig - source at http://ejohn.org/blog/javascript-micro-templating/
+ template = helpers.template = function(templateString, valuesObject){
+
+ // If templateString is function rather than string-template - call the function for valuesObject
+
+ if(templateString instanceof Function){
+ return templateString(valuesObject);
+ }
+
+ var cache = {};
+ function tmpl(str, data){
+ // Figure out if we're getting a template, or if we need to
+ // load the template - and be sure to cache the result.
+ var fn = !/\W/.test(str) ?
+ cache[str] = cache[str] :
+
+ // Generate a reusable function that will serve as a template
+ // generator (and which will be cached).
+ new Function("obj",
+ "var p=[],print=function(){p.push.apply(p,arguments);};" +
+
+ // Introduce the data as local variables using with(){}
+ "with(obj){p.push('" +
+
+ // Convert the template into pure JavaScript
+ str
+ .replace(/[\r\t\n]/g, " ")
+ .split("<%").join("\t")
+ .replace(/((^|%>)[^\t]*)'/g, "$1\r")
+ .replace(/\t=(.*?)%>/g, "',$1,'")
+ .split("\t").join("');")
+ .split("%>").join("p.push('")
+ .split("\r").join("\\'") +
+ "');}return p.join('');"
+ );
+
+ // Provide some basic currying to the user
+ return data ? fn( data ) : fn;
+ }
+ return tmpl(templateString,valuesObject);
+ },
+ /* jshint ignore:end */
+ generateLabels = helpers.generateLabels = function(templateString,numberOfSteps,graphMin,stepValue){
+ var labelsArray = new Array(numberOfSteps);
+ if (labelTemplateString){
+ each(labelsArray,function(val,index){
+ labelsArray[index] = template(templateString,{value: (graphMin + (stepValue*(index+1)))});
+ });
+ }
+ return labelsArray;
+ },
+ //--Animation methods
+ //Easing functions adapted from Robert Penner's easing equations
+ //http://www.robertpenner.com/easing/
+ easingEffects = helpers.easingEffects = {
+ linear: function (t) {
+ return t;
+ },
+ easeInQuad: function (t) {
+ return t * t;
+ },
+ easeOutQuad: function (t) {
+ return -1 * t * (t - 2);
+ },
+ easeInOutQuad: function (t) {
+ if ((t /= 1 / 2) < 1) return 1 / 2 * t * t;
+ return -1 / 2 * ((--t) * (t - 2) - 1);
+ },
+ easeInCubic: function (t) {
+ return t * t * t;
+ },
+ easeOutCubic: function (t) {
+ return 1 * ((t = t / 1 - 1) * t * t + 1);
+ },
+ easeInOutCubic: function (t) {
+ if ((t /= 1 / 2) < 1) return 1 / 2 * t * t * t;
+ return 1 / 2 * ((t -= 2) * t * t + 2);
+ },
+ easeInQuart: function (t) {
+ return t * t * t * t;
+ },
+ easeOutQuart: function (t) {
+ return -1 * ((t = t / 1 - 1) * t * t * t - 1);
+ },
+ easeInOutQuart: function (t) {
+ if ((t /= 1 / 2) < 1) return 1 / 2 * t * t * t * t;
+ return -1 / 2 * ((t -= 2) * t * t * t - 2);
+ },
+ easeInQuint: function (t) {
+ return 1 * (t /= 1) * t * t * t * t;
+ },
+ easeOutQuint: function (t) {
+ return 1 * ((t = t / 1 - 1) * t * t * t * t + 1);
+ },
+ easeInOutQuint: function (t) {
+ if ((t /= 1 / 2) < 1) return 1 / 2 * t * t * t * t * t;
+ return 1 / 2 * ((t -= 2) * t * t * t * t + 2);
+ },
+ easeInSine: function (t) {
+ return -1 * Math.cos(t / 1 * (Math.PI / 2)) + 1;
+ },
+ easeOutSine: function (t) {
+ return 1 * Math.sin(t / 1 * (Math.PI / 2));
+ },
+ easeInOutSine: function (t) {
+ return -1 / 2 * (Math.cos(Math.PI * t / 1) - 1);
+ },
+ easeInExpo: function (t) {
+ return (t === 0) ? 1 : 1 * Math.pow(2, 10 * (t / 1 - 1));
+ },
+ easeOutExpo: function (t) {
+ return (t === 1) ? 1 : 1 * (-Math.pow(2, -10 * t / 1) + 1);
+ },
+ easeInOutExpo: function (t) {
+ if (t === 0) return 0;
+ if (t === 1) return 1;
+ if ((t /= 1 / 2) < 1) return 1 / 2 * Math.pow(2, 10 * (t - 1));
+ return 1 / 2 * (-Math.pow(2, -10 * --t) + 2);
+ },
+ easeInCirc: function (t) {
+ if (t >= 1) return t;
+ return -1 * (Math.sqrt(1 - (t /= 1) * t) - 1);
+ },
+ easeOutCirc: function (t) {
+ return 1 * Math.sqrt(1 - (t = t / 1 - 1) * t);
+ },
+ easeInOutCirc: function (t) {
+ if ((t /= 1 / 2) < 1) return -1 / 2 * (Math.sqrt(1 - t * t) - 1);
+ return 1 / 2 * (Math.sqrt(1 - (t -= 2) * t) + 1);
+ },
+ easeInElastic: function (t) {
+ var s = 1.70158;
+ var p = 0;
+ var a = 1;
+ if (t === 0) return 0;
+ if ((t /= 1) == 1) return 1;
+ if (!p) p = 1 * 0.3;
+ if (a < Math.abs(1)) {
+ a = 1;
+ s = p / 4;
+ } else s = p / (2 * Math.PI) * Math.asin(1 / a);
+ return -(a * Math.pow(2, 10 * (t -= 1)) * Math.sin((t * 1 - s) * (2 * Math.PI) / p));
+ },
+ easeOutElastic: function (t) {
+ var s = 1.70158;
+ var p = 0;
+ var a = 1;
+ if (t === 0) return 0;
+ if ((t /= 1) == 1) return 1;
+ if (!p) p = 1 * 0.3;
+ if (a < Math.abs(1)) {
+ a = 1;
+ s = p / 4;
+ } else s = p / (2 * Math.PI) * Math.asin(1 / a);
+ return a * Math.pow(2, -10 * t) * Math.sin((t * 1 - s) * (2 * Math.PI) / p) + 1;
+ },
+ easeInOutElastic: function (t) {
+ var s = 1.70158;
+ var p = 0;
+ var a = 1;
+ if (t === 0) return 0;
+ if ((t /= 1 / 2) == 2) return 1;
+ if (!p) p = 1 * (0.3 * 1.5);
+ if (a < Math.abs(1)) {
+ a = 1;
+ s = p / 4;
+ } else s = p / (2 * Math.PI) * Math.asin(1 / a);
+ if (t < 1) return -0.5 * (a * Math.pow(2, 10 * (t -= 1)) * Math.sin((t * 1 - s) * (2 * Math.PI) / p));
+ return a * Math.pow(2, -10 * (t -= 1)) * Math.sin((t * 1 - s) * (2 * Math.PI) / p) * 0.5 + 1;
+ },
+ easeInBack: function (t) {
+ var s = 1.70158;
+ return 1 * (t /= 1) * t * ((s + 1) * t - s);
+ },
+ easeOutBack: function (t) {
+ var s = 1.70158;
+ return 1 * ((t = t / 1 - 1) * t * ((s + 1) * t + s) + 1);
+ },
+ easeInOutBack: function (t) {
+ var s = 1.70158;
+ if ((t /= 1 / 2) < 1) return 1 / 2 * (t * t * (((s *= (1.525)) + 1) * t - s));
+ return 1 / 2 * ((t -= 2) * t * (((s *= (1.525)) + 1) * t + s) + 2);
+ },
+ easeInBounce: function (t) {
+ return 1 - easingEffects.easeOutBounce(1 - t);
+ },
+ easeOutBounce: function (t) {
+ if ((t /= 1) < (1 / 2.75)) {
+ return 1 * (7.5625 * t * t);
+ } else if (t < (2 / 2.75)) {
+ return 1 * (7.5625 * (t -= (1.5 / 2.75)) * t + 0.75);
+ } else if (t < (2.5 / 2.75)) {
+ return 1 * (7.5625 * (t -= (2.25 / 2.75)) * t + 0.9375);
+ } else {
+ return 1 * (7.5625 * (t -= (2.625 / 2.75)) * t + 0.984375);
+ }
+ },
+ easeInOutBounce: function (t) {
+ if (t < 1 / 2) return easingEffects.easeInBounce(t * 2) * 0.5;
+ return easingEffects.easeOutBounce(t * 2 - 1) * 0.5 + 1 * 0.5;
+ }
+ },
+ //Request animation polyfill - http://www.paulirish.com/2011/requestanimationframe-for-smart-animating/
+ requestAnimFrame = helpers.requestAnimFrame = (function(){
+ return window.requestAnimationFrame ||
+ window.webkitRequestAnimationFrame ||
+ window.mozRequestAnimationFrame ||
+ window.oRequestAnimationFrame ||
+ window.msRequestAnimationFrame ||
+ function(callback) {
+ return window.setTimeout(callback, 1000 / 60);
+ };
+ })(),
+ cancelAnimFrame = helpers.cancelAnimFrame = (function(){
+ return window.cancelAnimationFrame ||
+ window.webkitCancelAnimationFrame ||
+ window.mozCancelAnimationFrame ||
+ window.oCancelAnimationFrame ||
+ window.msCancelAnimationFrame ||
+ function(callback) {
+ return window.clearTimeout(callback, 1000 / 60);
+ };
+ })(),
+ animationLoop = helpers.animationLoop = function(callback,totalSteps,easingString,onProgress,onComplete,chartInstance){
+
+ var currentStep = 0,
+ easingFunction = easingEffects[easingString] || easingEffects.linear;
+
+ var animationFrame = function(){
+ currentStep++;
+ var stepDecimal = currentStep/totalSteps;
+ var easeDecimal = easingFunction(stepDecimal);
+
+ callback.call(chartInstance,easeDecimal,stepDecimal, currentStep);
+ onProgress.call(chartInstance,easeDecimal,stepDecimal);
+ if (currentStep < totalSteps){
+ chartInstance.animationFrame = requestAnimFrame(animationFrame);
+ } else{
+ onComplete.apply(chartInstance);
+ }
+ };
+ requestAnimFrame(animationFrame);
+ },
+ //-- DOM methods
+ getRelativePosition = helpers.getRelativePosition = function(evt){
+ var mouseX, mouseY;
+ var e = evt.originalEvent || evt,
+ canvas = evt.currentTarget || evt.srcElement,
+ boundingRect = canvas.getBoundingClientRect();
+
+ if (e.touches){
+ mouseX = e.touches[0].clientX - boundingRect.left;
+ mouseY = e.touches[0].clientY - boundingRect.top;
+
+ }
+ else{
+ mouseX = e.clientX - boundingRect.left;
+ mouseY = e.clientY - boundingRect.top;
+ }
+
+ return {
+ x : mouseX,
+ y : mouseY
+ };
+
+ },
+ addEvent = helpers.addEvent = function(node,eventType,method){
+ if (node.addEventListener){
+ node.addEventListener(eventType,method);
+ } else if (node.attachEvent){
+ node.attachEvent("on"+eventType, method);
+ } else {
+ node["on"+eventType] = method;
+ }
+ },
+ removeEvent = helpers.removeEvent = function(node, eventType, handler){
+ if (node.removeEventListener){
+ node.removeEventListener(eventType, handler, false);
+ } else if (node.detachEvent){
+ node.detachEvent("on"+eventType,handler);
+ } else{
+ node["on" + eventType] = noop;
+ }
+ },
+ bindEvents = helpers.bindEvents = function(chartInstance, arrayOfEvents, handler){
+ // Create the events object if it's not already present
+ if (!chartInstance.events) chartInstance.events = {};
+
+ each(arrayOfEvents,function(eventName){
+ chartInstance.events[eventName] = function(){
+ handler.apply(chartInstance, arguments);
+ };
+ addEvent(chartInstance.chart.canvas,eventName,chartInstance.events[eventName]);
+ });
+ },
+ unbindEvents = helpers.unbindEvents = function (chartInstance, arrayOfEvents) {
+ each(arrayOfEvents, function(handler,eventName){
+ removeEvent(chartInstance.chart.canvas, eventName, handler);
+ });
+ },
+ getMaximumWidth = helpers.getMaximumWidth = function(domNode){
+ var container = domNode.parentNode;
+ // TODO = check cross browser stuff with this.
+ return container.clientWidth;
+ },
+ getMaximumHeight = helpers.getMaximumHeight = function(domNode){
+ var container = domNode.parentNode;
+ // TODO = check cross browser stuff with this.
+ return container.clientHeight;
+ },
+ getMaximumSize = helpers.getMaximumSize = helpers.getMaximumWidth, // legacy support
+ retinaScale = helpers.retinaScale = function(chart){
+ var ctx = chart.ctx,
+ width = chart.canvas.width,
+ height = chart.canvas.height;
+
+ if (window.devicePixelRatio) {
+ ctx.canvas.style.width = width + "px";
+ ctx.canvas.style.height = height + "px";
+ ctx.canvas.height = height * window.devicePixelRatio;
+ ctx.canvas.width = width * window.devicePixelRatio;
+ ctx.scale(window.devicePixelRatio, window.devicePixelRatio);
+ }
+ },
+ //-- Canvas methods
+ clear = helpers.clear = function(chart){
+ chart.ctx.clearRect(0,0,chart.width,chart.height);
+ },
+ fontString = helpers.fontString = function(pixelSize,fontStyle,fontFamily){
+ return fontStyle + " " + pixelSize+"px " + fontFamily;
+ },
+ longestText = helpers.longestText = function(ctx,font,arrayOfStrings){
+ ctx.font = font;
+ var longest = 0;
+ each(arrayOfStrings,function(string){
+ var textWidth = ctx.measureText(string).width;
+ longest = (textWidth > longest) ? textWidth : longest;
+ });
+ return longest;
+ },
+ drawRoundedRectangle = helpers.drawRoundedRectangle = function(ctx,x,y,width,height,radius){
+ ctx.beginPath();
+ ctx.moveTo(x + radius, y);
+ ctx.lineTo(x + width - radius, y);
+ ctx.quadraticCurveTo(x + width, y, x + width, y + radius);
+ ctx.lineTo(x + width, y + height - radius);
+ ctx.quadraticCurveTo(x + width, y + height, x + width - radius, y + height);
+ ctx.lineTo(x + radius, y + height);
+ ctx.quadraticCurveTo(x, y + height, x, y + height - radius);
+ ctx.lineTo(x, y + radius);
+ ctx.quadraticCurveTo(x, y, x + radius, y);
+ ctx.closePath();
+ };
+
+
+ //Store a reference to each instance - allowing us to globally resize chart instances on window resize.
+ //Destroy method on the chart will remove the instance of the chart from this reference.
+ Chart.instances = {};
+
+ Chart.Type = function(data,options,chart){
+ this.options = options;
+ this.chart = chart;
+ this.id = uid();
+ //Add the chart instance to the global namespace
+ Chart.instances[this.id] = this;
+
+ // Initialize is always called when a chart type is created
+ // By default it is a no op, but it should be extended
+ if (options.responsive){
+ this.resize();
+ }
+ this.initialize.call(this,data);
+ };
+
+ //Core methods that'll be a part of every chart type
+ extend(Chart.Type.prototype,{
+ initialize : function(){return this;},
+ clear : function(){
+ clear(this.chart);
+ return this;
+ },
+ stop : function(){
+ // Stops any current animation loop occuring
+ cancelAnimFrame(this.animationFrame);
+ return this;
+ },
+ resize : function(callback){
+ this.stop();
+ var canvas = this.chart.canvas,
+ newWidth = getMaximumWidth(this.chart.canvas),
+ newHeight = this.options.maintainAspectRatio ? newWidth / this.chart.aspectRatio : getMaximumHeight(this.chart.canvas);
+
+ canvas.width = this.chart.width = newWidth;
+ canvas.height = this.chart.height = newHeight;
+
+ retinaScale(this.chart);
+
+ if (typeof callback === "function"){
+ callback.apply(this, Array.prototype.slice.call(arguments, 1));
+ }
+ return this;
+ },
+ reflow : noop,
+ render : function(reflow){
+ if (reflow){
+ this.reflow();
+ }
+ if (this.options.animation && !reflow){
+ helpers.animationLoop(
+ this.draw,
+ this.options.animationSteps,
+ this.options.animationEasing,
+ this.options.onAnimationProgress,
+ this.options.onAnimationComplete,
+ this
+ );
+ }
+ else{
+ this.draw();
+ this.options.onAnimationComplete.call(this);
+ }
+ return this;
+ },
+ generateLegend : function(){
+ return template(this.options.legendTemplate,this);
+ },
+ destroy : function(){
+ this.clear();
+ unbindEvents(this, this.events);
+ var canvas = this.chart.canvas;
+
+ // Reset canvas height/width attributes starts a fresh with the canvas context
+ canvas.width = this.chart.width;
+ canvas.height = this.chart.height;
+
+ // < IE9 doesn't support removeProperty
+ if (canvas.style.removeProperty) {
+ canvas.style.removeProperty('width');
+ canvas.style.removeProperty('height');
+ } else {
+ canvas.style.removeAttribute('width');
+ canvas.style.removeAttribute('height');
+ }
+
+ delete Chart.instances[this.id];
+ },
+ showTooltip : function(ChartElements, forceRedraw){
+ // Only redraw the chart if we've actually changed what we're hovering on.
+ if (typeof this.activeElements === 'undefined') this.activeElements = [];
+
+ var isChanged = (function(Elements){
+ var changed = false;
+
+ if (Elements.length !== this.activeElements.length){
+ changed = true;
+ return changed;
+ }
+
+ each(Elements, function(element, index){
+ if (element !== this.activeElements[index]){
+ changed = true;
+ }
+ }, this);
+ return changed;
+ }).call(this, ChartElements);
+
+ if (!isChanged && !forceRedraw){
+ return;
+ }
+ else{
+ this.activeElements = ChartElements;
+ }
+ this.draw();
+ if(this.options.customTooltips){
+ this.options.customTooltips(false);
+ }
+ if (ChartElements.length > 0){
+ // If we have multiple datasets, show a MultiTooltip for all of the data points at that index
+ if (this.datasets && this.datasets.length > 1) {
+ var dataArray,
+ dataIndex;
+
+ for (var i = this.datasets.length - 1; i >= 0; i--) {
+ dataArray = this.datasets[i].points || this.datasets[i].bars || this.datasets[i].segments;
+ dataIndex = indexOf(dataArray, ChartElements[0]);
+ if (dataIndex !== -1){
+ break;
+ }
+ }
+ var tooltipLabels = [],
+ tooltipColors = [],
+ medianPosition = (function(index) {
+
+ // Get all the points at that particular index
+ var Elements = [],
+ dataCollection,
+ xPositions = [],
+ yPositions = [],
+ xMax,
+ yMax,
+ xMin,
+ yMin;
+ helpers.each(this.datasets, function(dataset){
+ dataCollection = dataset.points || dataset.bars || dataset.segments;
+ if (dataCollection[dataIndex] && dataCollection[dataIndex].hasValue()){
+ Elements.push(dataCollection[dataIndex]);
+ }
+ });
+
+ helpers.each(Elements, function(element) {
+ xPositions.push(element.x);
+ yPositions.push(element.y);
+
+
+ //Include any colour information about the element
+ tooltipLabels.push(helpers.template(this.options.multiTooltipTemplate, element));
+ tooltipColors.push({
+ fill: element._saved.fillColor || element.fillColor,
+ stroke: element._saved.strokeColor || element.strokeColor
+ });
+
+ }, this);
+
+ yMin = min(yPositions);
+ yMax = max(yPositions);
+
+ xMin = min(xPositions);
+ xMax = max(xPositions);
+
+ return {
+ x: (xMin > this.chart.width/2) ? xMin : xMax,
+ y: (yMin + yMax)/2
+ };
+ }).call(this, dataIndex);
+
+ new Chart.MultiTooltip({
+ x: medianPosition.x,
+ y: medianPosition.y,
+ xPadding: this.options.tooltipXPadding,
+ yPadding: this.options.tooltipYPadding,
+ xOffset: this.options.tooltipXOffset,
+ fillColor: this.options.tooltipFillColor,
+ textColor: this.options.tooltipFontColor,
+ fontFamily: this.options.tooltipFontFamily,
+ fontStyle: this.options.tooltipFontStyle,
+ fontSize: this.options.tooltipFontSize,
+ titleTextColor: this.options.tooltipTitleFontColor,
+ titleFontFamily: this.options.tooltipTitleFontFamily,
+ titleFontStyle: this.options.tooltipTitleFontStyle,
+ titleFontSize: this.options.tooltipTitleFontSize,
+ cornerRadius: this.options.tooltipCornerRadius,
+ labels: tooltipLabels,
+ legendColors: tooltipColors,
+ legendColorBackground : this.options.multiTooltipKeyBackground,
+ title: ChartElements[0].label,
+ chart: this.chart,
+ ctx: this.chart.ctx,
+ custom: this.options.customTooltips
+ }).draw();
+
+ } else {
+ each(ChartElements, function(Element) {
+ var tooltipPosition = Element.tooltipPosition();
+ new Chart.Tooltip({
+ x: Math.round(tooltipPosition.x),
+ y: Math.round(tooltipPosition.y),
+ xPadding: this.options.tooltipXPadding,
+ yPadding: this.options.tooltipYPadding,
+ fillColor: this.options.tooltipFillColor,
+ textColor: this.options.tooltipFontColor,
+ fontFamily: this.options.tooltipFontFamily,
+ fontStyle: this.options.tooltipFontStyle,
+ fontSize: this.options.tooltipFontSize,
+ caretHeight: this.options.tooltipCaretSize,
+ cornerRadius: this.options.tooltipCornerRadius,
+ text: template(this.options.tooltipTemplate, Element),
+ chart: this.chart,
+ custom: this.options.customTooltips
+ }).draw();
+ }, this);
+ }
+ }
+ return this;
+ },
+ toBase64Image : function(){
+ return this.chart.canvas.toDataURL.apply(this.chart.canvas, arguments);
+ }
+ });
+
+ Chart.Type.extend = function(extensions){
+
+ var parent = this;
+
+ var ChartType = function(){
+ return parent.apply(this,arguments);
+ };
+
+ //Copy the prototype object of the this class
+ ChartType.prototype = clone(parent.prototype);
+ //Now overwrite some of the properties in the base class with the new extensions
+ extend(ChartType.prototype, extensions);
+
+ ChartType.extend = Chart.Type.extend;
+
+ if (extensions.name || parent.prototype.name){
+
+ var chartName = extensions.name || parent.prototype.name;
+ //Assign any potential default values of the new chart type
+
+ //If none are defined, we'll use a clone of the chart type this is being extended from.
+ //I.e. if we extend a line chart, we'll use the defaults from the line chart if our new chart
+ //doesn't define some defaults of their own.
+
+ var baseDefaults = (Chart.defaults[parent.prototype.name]) ? clone(Chart.defaults[parent.prototype.name]) : {};
+
+ Chart.defaults[chartName] = extend(baseDefaults,extensions.defaults);
+
+ Chart.types[chartName] = ChartType;
+
+ //Register this new chart type in the Chart prototype
+ Chart.prototype[chartName] = function(data,options){
+ var config = merge(Chart.defaults.global, Chart.defaults[chartName], options || {});
+ return new ChartType(data,config,this);
+ };
+ } else{
+ warn("Name not provided for this chart, so it hasn't been registered");
+ }
+ return parent;
+ };
+
+ Chart.Element = function(configuration){
+ extend(this,configuration);
+ this.initialize.apply(this,arguments);
+ this.save();
+ };
+ extend(Chart.Element.prototype,{
+ initialize : function(){},
+ restore : function(props){
+ if (!props){
+ extend(this,this._saved);
+ } else {
+ each(props,function(key){
+ this[key] = this._saved[key];
+ },this);
+ }
+ return this;
+ },
+ save : function(){
+ this._saved = clone(this);
+ delete this._saved._saved;
+ return this;
+ },
+ update : function(newProps){
+ each(newProps,function(value,key){
+ this._saved[key] = this[key];
+ this[key] = value;
+ },this);
+ return this;
+ },
+ transition : function(props,ease){
+ each(props,function(value,key){
+ this[key] = ((value - this._saved[key]) * ease) + this._saved[key];
+ },this);
+ return this;
+ },
+ tooltipPosition : function(){
+ return {
+ x : this.x,
+ y : this.y
+ };
+ },
+ hasValue: function(){
+ return isNumber(this.value);
+ }
+ });
+
+ Chart.Element.extend = inherits;
+
+
+ Chart.Point = Chart.Element.extend({
+ display: true,
+ inRange: function(chartX,chartY){
+ var hitDetectionRange = this.hitDetectionRadius + this.radius;
+ return ((Math.pow(chartX-this.x, 2)+Math.pow(chartY-this.y, 2)) < Math.pow(hitDetectionRange,2));
+ },
+ draw : function(){
+ if (this.display){
+ var ctx = this.ctx;
+ ctx.beginPath();
+
+ ctx.arc(this.x, this.y, this.radius, 0, Math.PI*2);
+ ctx.closePath();
+
+ ctx.strokeStyle = this.strokeColor;
+ ctx.lineWidth = this.strokeWidth;
+
+ ctx.fillStyle = this.fillColor;
+
+ ctx.fill();
+ ctx.stroke();
+ }
+
+
+ //Quick debug for bezier curve splining
+ //Highlights control points and the line between them.
+ //Handy for dev - stripped in the min version.
+
+ // ctx.save();
+ // ctx.fillStyle = "black";
+ // ctx.strokeStyle = "black"
+ // ctx.beginPath();
+ // ctx.arc(this.controlPoints.inner.x,this.controlPoints.inner.y, 2, 0, Math.PI*2);
+ // ctx.fill();
+
+ // ctx.beginPath();
+ // ctx.arc(this.controlPoints.outer.x,this.controlPoints.outer.y, 2, 0, Math.PI*2);
+ // ctx.fill();
+
+ // ctx.moveTo(this.controlPoints.inner.x,this.controlPoints.inner.y);
+ // ctx.lineTo(this.x, this.y);
+ // ctx.lineTo(this.controlPoints.outer.x,this.controlPoints.outer.y);
+ // ctx.stroke();
+
+ // ctx.restore();
+
+
+
+ }
+ });
+
+ Chart.Arc = Chart.Element.extend({
+ inRange : function(chartX,chartY){
+
+ var pointRelativePosition = helpers.getAngleFromPoint(this, {
+ x: chartX,
+ y: chartY
+ });
+
+ //Check if within the range of the open/close angle
+ var betweenAngles = (pointRelativePosition.angle >= this.startAngle && pointRelativePosition.angle <= this.endAngle),
+ withinRadius = (pointRelativePosition.distance >= this.innerRadius && pointRelativePosition.distance <= this.outerRadius);
+
+ return (betweenAngles && withinRadius);
+ //Ensure within the outside of the arc centre, but inside arc outer
+ },
+ tooltipPosition : function(){
+ var centreAngle = this.startAngle + ((this.endAngle - this.startAngle) / 2),
+ rangeFromCentre = (this.outerRadius - this.innerRadius) / 2 + this.innerRadius;
+ return {
+ x : this.x + (Math.cos(centreAngle) * rangeFromCentre),
+ y : this.y + (Math.sin(centreAngle) * rangeFromCentre)
+ };
+ },
+ draw : function(animationPercent){
+
+ var easingDecimal = animationPercent || 1;
+
+ var ctx = this.ctx;
+
+ ctx.beginPath();
+
+ ctx.arc(this.x, this.y, this.outerRadius, this.startAngle, this.endAngle);
+
+ ctx.arc(this.x, this.y, this.innerRadius, this.endAngle, this.startAngle, true);
+
+ ctx.closePath();
+ ctx.strokeStyle = this.strokeColor;
+ ctx.lineWidth = this.strokeWidth;
+
+ ctx.fillStyle = this.fillColor;
+
+ ctx.fill();
+ ctx.lineJoin = 'bevel';
+
+ if (this.showStroke){
+ ctx.stroke();
+ }
+ }
+ });
+
+ Chart.Rectangle = Chart.Element.extend({
+ draw : function(){
+ var ctx = this.ctx,
+ halfWidth = this.width/2,
+ leftX = this.x - halfWidth,
+ rightX = this.x + halfWidth,
+ top = this.base - (this.base - this.y),
+ halfStroke = this.strokeWidth / 2;
+
+ // Canvas doesn't allow us to stroke inside the width so we can
+ // adjust the sizes to fit if we're setting a stroke on the line
+ if (this.showStroke){
+ leftX += halfStroke;
+ rightX -= halfStroke;
+ top += halfStroke;
+ }
+
+ ctx.beginPath();
+
+ ctx.fillStyle = this.fillColor;
+ ctx.strokeStyle = this.strokeColor;
+ ctx.lineWidth = this.strokeWidth;
+
+ // It'd be nice to keep this class totally generic to any rectangle
+ // and simply specify which border to miss out.
+ ctx.moveTo(leftX, this.base);
+ ctx.lineTo(leftX, top);
+ ctx.lineTo(rightX, top);
+ ctx.lineTo(rightX, this.base);
+ ctx.fill();
+ if (this.showStroke){
+ ctx.stroke();
+ }
+ },
+ height : function(){
+ return this.base - this.y;
+ },
+ inRange : function(chartX,chartY){
+ return (chartX >= this.x - this.width/2 && chartX <= this.x + this.width/2) && (chartY >= this.y && chartY <= this.base);
+ }
+ });
+
+ Chart.Tooltip = Chart.Element.extend({
+ draw : function(){
+
+ var ctx = this.chart.ctx;
+
+ ctx.font = fontString(this.fontSize,this.fontStyle,this.fontFamily);
+
+ this.xAlign = "center";
+ this.yAlign = "above";
+
+ //Distance between the actual element.y position and the start of the tooltip caret
+ var caretPadding = this.caretPadding = 2;
+
+ var tooltipWidth = ctx.measureText(this.text).width + 2*this.xPadding,
+ tooltipRectHeight = this.fontSize + 2*this.yPadding,
+ tooltipHeight = tooltipRectHeight + this.caretHeight + caretPadding;
+
+ if (this.x + tooltipWidth/2 >this.chart.width){
+ this.xAlign = "left";
+ } else if (this.x - tooltipWidth/2 < 0){
+ this.xAlign = "right";
+ }
+
+ if (this.y - tooltipHeight < 0){
+ this.yAlign = "below";
+ }
+
+
+ var tooltipX = this.x - tooltipWidth/2,
+ tooltipY = this.y - tooltipHeight;
+
+ ctx.fillStyle = this.fillColor;
+
+ // Custom Tooltips
+ if(this.custom){
+ this.custom(this);
+ }
+ else{
+ switch(this.yAlign)
+ {
+ case "above":
+ //Draw a caret above the x/y
+ ctx.beginPath();
+ ctx.moveTo(this.x,this.y - caretPadding);
+ ctx.lineTo(this.x + this.caretHeight, this.y - (caretPadding + this.caretHeight));
+ ctx.lineTo(this.x - this.caretHeight, this.y - (caretPadding + this.caretHeight));
+ ctx.closePath();
+ ctx.fill();
+ break;
+ case "below":
+ tooltipY = this.y + caretPadding + this.caretHeight;
+ //Draw a caret below the x/y
+ ctx.beginPath();
+ ctx.moveTo(this.x, this.y + caretPadding);
+ ctx.lineTo(this.x + this.caretHeight, this.y + caretPadding + this.caretHeight);
+ ctx.lineTo(this.x - this.caretHeight, this.y + caretPadding + this.caretHeight);
+ ctx.closePath();
+ ctx.fill();
+ break;
+ }
+
+ switch(this.xAlign)
+ {
+ case "left":
+ tooltipX = this.x - tooltipWidth + (this.cornerRadius + this.caretHeight);
+ break;
+ case "right":
+ tooltipX = this.x - (this.cornerRadius + this.caretHeight);
+ break;
+ }
+
+ drawRoundedRectangle(ctx,tooltipX,tooltipY,tooltipWidth,tooltipRectHeight,this.cornerRadius);
+
+ ctx.fill();
+
+ ctx.fillStyle = this.textColor;
+ ctx.textAlign = "center";
+ ctx.textBaseline = "middle";
+ ctx.fillText(this.text, tooltipX + tooltipWidth/2, tooltipY + tooltipRectHeight/2);
+ }
+ }
+ });
+
+ Chart.MultiTooltip = Chart.Element.extend({
+ initialize : function(){
+ this.font = fontString(this.fontSize,this.fontStyle,this.fontFamily);
+
+ this.titleFont = fontString(this.titleFontSize,this.titleFontStyle,this.titleFontFamily);
+
+ this.height = (this.labels.length * this.fontSize) + ((this.labels.length-1) * (this.fontSize/2)) + (this.yPadding*2) + this.titleFontSize *1.5;
+
+ this.ctx.font = this.titleFont;
+
+ var titleWidth = this.ctx.measureText(this.title).width,
+ //Label has a legend square as well so account for this.
+ labelWidth = longestText(this.ctx,this.font,this.labels) + this.fontSize + 3,
+ longestTextWidth = max([labelWidth,titleWidth]);
+
+ this.width = longestTextWidth + (this.xPadding*2);
+
+
+ var halfHeight = this.height/2;
+
+ //Check to ensure the height will fit on the canvas
+ if (this.y - halfHeight < 0 ){
+ this.y = halfHeight;
+ } else if (this.y + halfHeight > this.chart.height){
+ this.y = this.chart.height - halfHeight;
+ }
+
+ //Decide whether to align left or right based on position on canvas
+ if (this.x > this.chart.width/2){
+ this.x -= this.xOffset + this.width;
+ } else {
+ this.x += this.xOffset;
+ }
+
+
+ },
+ getLineHeight : function(index){
+ var baseLineHeight = this.y - (this.height/2) + this.yPadding,
+ afterTitleIndex = index-1;
+
+ //If the index is zero, we're getting the title
+ if (index === 0){
+ return baseLineHeight + this.titleFontSize/2;
+ } else{
+ return baseLineHeight + ((this.fontSize*1.5*afterTitleIndex) + this.fontSize/2) + this.titleFontSize * 1.5;
+ }
+
+ },
+ draw : function(){
+ // Custom Tooltips
+ if(this.custom){
+ this.custom(this);
+ }
+ else{
+ drawRoundedRectangle(this.ctx,this.x,this.y - this.height/2,this.width,this.height,this.cornerRadius);
+ var ctx = this.ctx;
+ ctx.fillStyle = this.fillColor;
+ ctx.fill();
+ ctx.closePath();
+
+ ctx.textAlign = "left";
+ ctx.textBaseline = "middle";
+ ctx.fillStyle = this.titleTextColor;
+ ctx.font = this.titleFont;
+
+ ctx.fillText(this.title,this.x + this.xPadding, this.getLineHeight(0));
+
+ ctx.font = this.font;
+ helpers.each(this.labels,function(label,index){
+ ctx.fillStyle = this.textColor;
+ ctx.fillText(label,this.x + this.xPadding + this.fontSize + 3, this.getLineHeight(index + 1));
+
+ //A bit gnarly, but clearing this rectangle breaks when using explorercanvas (clears whole canvas)
+ //ctx.clearRect(this.x + this.xPadding, this.getLineHeight(index + 1) - this.fontSize/2, this.fontSize, this.fontSize);
+ //Instead we'll make a white filled block to put the legendColour palette over.
+
+ ctx.fillStyle = this.legendColorBackground;
+ ctx.fillRect(this.x + this.xPadding, this.getLineHeight(index + 1) - this.fontSize/2, this.fontSize, this.fontSize);
+
+ ctx.fillStyle = this.legendColors[index].fill;
+ ctx.fillRect(this.x + this.xPadding, this.getLineHeight(index + 1) - this.fontSize/2, this.fontSize, this.fontSize);
+
+
+ },this);
+ }
+ }
+ });
+
+ Chart.Scale = Chart.Element.extend({
+ initialize : function(){
+ this.fit();
+ },
+ buildYLabels : function(){
+ this.yLabels = [];
+
+ var stepDecimalPlaces = getDecimalPlaces(this.stepValue);
+
+ for (var i=0; i<=this.steps; i++){
+ this.yLabels.push(template(this.templateString,{value:(this.min + (i * this.stepValue)).toFixed(stepDecimalPlaces)}));
+ }
+ this.yLabelWidth = (this.display && this.showLabels) ? longestText(this.ctx,this.font,this.yLabels) : 0;
+ },
+ addXLabel : function(label){
+ this.xLabels.push(label);
+ this.valuesCount++;
+ this.fit();
+ },
+ removeXLabel : function(){
+ this.xLabels.shift();
+ this.valuesCount--;
+ this.fit();
+ },
+ // Fitting loop to rotate x Labels and figure out what fits there, and also calculate how many Y steps to use
+ fit: function(){
+ // First we need the width of the yLabels, assuming the xLabels aren't rotated
+
+ // To do that we need the base line at the top and base of the chart, assuming there is no x label rotation
+ this.startPoint = (this.display) ? this.fontSize : 0;
+ this.endPoint = (this.display) ? this.height - (this.fontSize * 1.5) - 5 : this.height; // -5 to pad labels
+
+ // Apply padding settings to the start and end point.
+ this.startPoint += this.padding;
+ this.endPoint -= this.padding;
+
+ // Cache the starting height, so can determine if we need to recalculate the scale yAxis
+ var cachedHeight = this.endPoint - this.startPoint,
+ cachedYLabelWidth;
+
+ // Build the current yLabels so we have an idea of what size they'll be to start
+ /*
+ * This sets what is returned from calculateScaleRange as static properties of this class:
+ *
+ this.steps;
+ this.stepValue;
+ this.min;
+ this.max;
+ *
+ */
+ this.calculateYRange(cachedHeight);
+
+ // With these properties set we can now build the array of yLabels
+ // and also the width of the largest yLabel
+ this.buildYLabels();
+
+ this.calculateXLabelRotation();
+
+ while((cachedHeight > this.endPoint - this.startPoint)){
+ cachedHeight = this.endPoint - this.startPoint;
+ cachedYLabelWidth = this.yLabelWidth;
+
+ this.calculateYRange(cachedHeight);
+ this.buildYLabels();
+
+ // Only go through the xLabel loop again if the yLabel width has changed
+ if (cachedYLabelWidth < this.yLabelWidth){
+ this.calculateXLabelRotation();
+ }
+ }
+
+ },
+ calculateXLabelRotation : function(){
+ //Get the width of each grid by calculating the difference
+ //between x offsets between 0 and 1.
+
+ this.ctx.font = this.font;
+
+ var firstWidth = this.ctx.measureText(this.xLabels[0]).width,
+ lastWidth = this.ctx.measureText(this.xLabels[this.xLabels.length - 1]).width,
+ firstRotated,
+ lastRotated;
+
+
+ this.xScalePaddingRight = lastWidth/2 + 3;
+ this.xScalePaddingLeft = (firstWidth/2 > this.yLabelWidth + 10) ? firstWidth/2 : this.yLabelWidth + 10;
+
+ this.xLabelRotation = 0;
+ if (this.display){
+ var originalLabelWidth = longestText(this.ctx,this.font,this.xLabels),
+ cosRotation,
+ firstRotatedWidth;
+ this.xLabelWidth = originalLabelWidth;
+ //Allow 3 pixels x2 padding either side for label readability
+ var xGridWidth = Math.floor(this.calculateX(1) - this.calculateX(0)) - 6;
+
+ //Max label rotate should be 90 - also act as a loop counter
+ while ((this.xLabelWidth > xGridWidth && this.xLabelRotation === 0) || (this.xLabelWidth > xGridWidth && this.xLabelRotation <= 90 && this.xLabelRotation > 0)){
+ cosRotation = Math.cos(toRadians(this.xLabelRotation));
+
+ firstRotated = cosRotation * firstWidth;
+ lastRotated = cosRotation * lastWidth;
+
+ // We're right aligning the text now.
+ if (firstRotated + this.fontSize / 2 > this.yLabelWidth + 8){
+ this.xScalePaddingLeft = firstRotated + this.fontSize / 2;
+ }
+ this.xScalePaddingRight = this.fontSize/2;
+
+
+ this.xLabelRotation++;
+ this.xLabelWidth = cosRotation * originalLabelWidth;
+
+ }
+ if (this.xLabelRotation > 0){
+ this.endPoint -= Math.sin(toRadians(this.xLabelRotation))*originalLabelWidth + 3;
+ }
+ }
+ else{
+ this.xLabelWidth = 0;
+ this.xScalePaddingRight = this.padding;
+ this.xScalePaddingLeft = this.padding;
+ }
+
+ },
+ // Needs to be overidden in each Chart type
+ // Otherwise we need to pass all the data into the scale class
+ calculateYRange: noop,
+ drawingArea: function(){
+ return this.startPoint - this.endPoint;
+ },
+ calculateY : function(value){
+ var scalingFactor = this.drawingArea() / (this.min - this.max);
+ return this.endPoint - (scalingFactor * (value - this.min));
+ },
+ calculateX : function(index){
+ var isRotated = (this.xLabelRotation > 0),
+ // innerWidth = (this.offsetGridLines) ? this.width - offsetLeft - this.padding : this.width - (offsetLeft + halfLabelWidth * 2) - this.padding,
+ innerWidth = this.width - (this.xScalePaddingLeft + this.xScalePaddingRight),
+ valueWidth = innerWidth/Math.max((this.valuesCount - ((this.offsetGridLines) ? 0 : 1)), 1),
+ valueOffset = (valueWidth * index) + this.xScalePaddingLeft;
+
+ if (this.offsetGridLines){
+ valueOffset += (valueWidth/2);
+ }
+
+ return Math.round(valueOffset);
+ },
+ update : function(newProps){
+ helpers.extend(this, newProps);
+ this.fit();
+ },
+ draw : function(){
+ var ctx = this.ctx,
+ yLabelGap = (this.endPoint - this.startPoint) / this.steps,
+ xStart = Math.round(this.xScalePaddingLeft);
+ if (this.display){
+ ctx.fillStyle = this.textColor;
+ ctx.font = this.font;
+ each(this.yLabels,function(labelString,index){
+ var yLabelCenter = this.endPoint - (yLabelGap * index),
+ linePositionY = Math.round(yLabelCenter),
+ drawHorizontalLine = this.showHorizontalLines;
+
+ ctx.textAlign = "right";
+ ctx.textBaseline = "middle";
+ if (this.showLabels){
+ ctx.fillText(labelString,xStart - 10,yLabelCenter);
+ }
+
+ // This is X axis, so draw it
+ if (index === 0 && !drawHorizontalLine){
+ drawHorizontalLine = true;
+ }
+
+ if (drawHorizontalLine){
+ ctx.beginPath();
+ }
+
+ if (index > 0){
+ // This is a grid line in the centre, so drop that
+ ctx.lineWidth = this.gridLineWidth;
+ ctx.strokeStyle = this.gridLineColor;
+ } else {
+ // This is the first line on the scale
+ ctx.lineWidth = this.lineWidth;
+ ctx.strokeStyle = this.lineColor;
+ }
+
+ linePositionY += helpers.aliasPixel(ctx.lineWidth);
+
+ if(drawHorizontalLine){
+ ctx.moveTo(xStart, linePositionY);
+ ctx.lineTo(this.width, linePositionY);
+ ctx.stroke();
+ ctx.closePath();
+ }
+
+ ctx.lineWidth = this.lineWidth;
+ ctx.strokeStyle = this.lineColor;
+ ctx.beginPath();
+ ctx.moveTo(xStart - 5, linePositionY);
+ ctx.lineTo(xStart, linePositionY);
+ ctx.stroke();
+ ctx.closePath();
+
+ },this);
+
+ each(this.xLabels,function(label,index){
+ var xPos = this.calculateX(index) + aliasPixel(this.lineWidth),
+ // Check to see if line/bar here and decide where to place the line
+ linePos = this.calculateX(index - (this.offsetGridLines ? 0.5 : 0)) + aliasPixel(this.lineWidth),
+ isRotated = (this.xLabelRotation > 0),
+ drawVerticalLine = this.showVerticalLines;
+
+ // This is Y axis, so draw it
+ if (index === 0 && !drawVerticalLine){
+ drawVerticalLine = true;
+ }
+
+ if (drawVerticalLine){
+ ctx.beginPath();
+ }
+
+ if (index > 0){
+ // This is a grid line in the centre, so drop that
+ ctx.lineWidth = this.gridLineWidth;
+ ctx.strokeStyle = this.gridLineColor;
+ } else {
+ // This is the first line on the scale
+ ctx.lineWidth = this.lineWidth;
+ ctx.strokeStyle = this.lineColor;
+ }
+
+ if (drawVerticalLine){
+ ctx.moveTo(linePos,this.endPoint);
+ ctx.lineTo(linePos,this.startPoint - 3);
+ ctx.stroke();
+ ctx.closePath();
+ }
+
+
+ ctx.lineWidth = this.lineWidth;
+ ctx.strokeStyle = this.lineColor;
+
+
+ // Small lines at the bottom of the base grid line
+ ctx.beginPath();
+ ctx.moveTo(linePos,this.endPoint);
+ ctx.lineTo(linePos,this.endPoint + 5);
+ ctx.stroke();
+ ctx.closePath();
+
+ ctx.save();
+ ctx.translate(xPos,(isRotated) ? this.endPoint + 12 : this.endPoint + 8);
+ ctx.rotate(toRadians(this.xLabelRotation)*-1);
+ ctx.font = this.font;
+ ctx.textAlign = (isRotated) ? "right" : "center";
+ ctx.textBaseline = (isRotated) ? "middle" : "top";
+ ctx.fillText(label, 0, 0);
+ ctx.restore();
+ },this);
+
+ }
+ }
+
+ });
+
+ Chart.RadialScale = Chart.Element.extend({
+ initialize: function(){
+ this.size = min([this.height, this.width]);
+ this.drawingArea = (this.display) ? (this.size/2) - (this.fontSize/2 + this.backdropPaddingY) : (this.size/2);
+ },
+ calculateCenterOffset: function(value){
+ // Take into account half font size + the yPadding of the top value
+ var scalingFactor = this.drawingArea / (this.max - this.min);
+
+ return (value - this.min) * scalingFactor;
+ },
+ update : function(){
+ if (!this.lineArc){
+ this.setScaleSize();
+ } else {
+ this.drawingArea = (this.display) ? (this.size/2) - (this.fontSize/2 + this.backdropPaddingY) : (this.size/2);
+ }
+ this.buildYLabels();
+ },
+ buildYLabels: function(){
+ this.yLabels = [];
+
+ var stepDecimalPlaces = getDecimalPlaces(this.stepValue);
+
+ for (var i=0; i<=this.steps; i++){
+ this.yLabels.push(template(this.templateString,{value:(this.min + (i * this.stepValue)).toFixed(stepDecimalPlaces)}));
+ }
+ },
+ getCircumference : function(){
+ return ((Math.PI*2) / this.valuesCount);
+ },
+ setScaleSize: function(){
+ /*
+ * Right, this is really confusing and there is a lot of maths going on here
+ * The gist of the problem is here: https://gist.github.com/nnnick/696cc9c55f4b0beb8fe9
+ *
+ * Reaction: https://dl.dropboxusercontent.com/u/34601363/toomuchscience.gif
+ *
+ * Solution:
+ *
+ * We assume the radius of the polygon is half the size of the canvas at first
+ * at each index we check if the text overlaps.
+ *
+ * Where it does, we store that angle and that index.
+ *
+ * After finding the largest index and angle we calculate how much we need to remove
+ * from the shape radius to move the point inwards by that x.
+ *
+ * We average the left and right distances to get the maximum shape radius that can fit in the box
+ * along with labels.
+ *
+ * Once we have that, we can find the centre point for the chart, by taking the x text protrusion
+ * on each side, removing that from the size, halving it and adding the left x protrusion width.
+ *
+ * This will mean we have a shape fitted to the canvas, as large as it can be with the labels
+ * and position it in the most space efficient manner
+ *
+ * https://dl.dropboxusercontent.com/u/34601363/yeahscience.gif
+ */
+
+
+ // Get maximum radius of the polygon. Either half the height (minus the text width) or half the width.
+ // Use this to calculate the offset + change. - Make sure L/R protrusion is at least 0 to stop issues with centre points
+ var largestPossibleRadius = min([(this.height/2 - this.pointLabelFontSize - 5), this.width/2]),
+ pointPosition,
+ i,
+ textWidth,
+ halfTextWidth,
+ furthestRight = this.width,
+ furthestRightIndex,
+ furthestRightAngle,
+ furthestLeft = 0,
+ furthestLeftIndex,
+ furthestLeftAngle,
+ xProtrusionLeft,
+ xProtrusionRight,
+ radiusReductionRight,
+ radiusReductionLeft,
+ maxWidthRadius;
+ this.ctx.font = fontString(this.pointLabelFontSize,this.pointLabelFontStyle,this.pointLabelFontFamily);
+ for (i=0;i furthestRight) {
+ furthestRight = pointPosition.x + halfTextWidth;
+ furthestRightIndex = i;
+ }
+ if (pointPosition.x - halfTextWidth < furthestLeft) {
+ furthestLeft = pointPosition.x - halfTextWidth;
+ furthestLeftIndex = i;
+ }
+ }
+ else if (i < this.valuesCount/2) {
+ // Less than half the values means we'll left align the text
+ if (pointPosition.x + textWidth > furthestRight) {
+ furthestRight = pointPosition.x + textWidth;
+ furthestRightIndex = i;
+ }
+ }
+ else if (i > this.valuesCount/2){
+ // More than half the values means we'll right align the text
+ if (pointPosition.x - textWidth < furthestLeft) {
+ furthestLeft = pointPosition.x - textWidth;
+ furthestLeftIndex = i;
+ }
+ }
+ }
+
+ xProtrusionLeft = furthestLeft;
+
+ xProtrusionRight = Math.ceil(furthestRight - this.width);
+
+ furthestRightAngle = this.getIndexAngle(furthestRightIndex);
+
+ furthestLeftAngle = this.getIndexAngle(furthestLeftIndex);
+
+ radiusReductionRight = xProtrusionRight / Math.sin(furthestRightAngle + Math.PI/2);
+
+ radiusReductionLeft = xProtrusionLeft / Math.sin(furthestLeftAngle + Math.PI/2);
+
+ // Ensure we actually need to reduce the size of the chart
+ radiusReductionRight = (isNumber(radiusReductionRight)) ? radiusReductionRight : 0;
+ radiusReductionLeft = (isNumber(radiusReductionLeft)) ? radiusReductionLeft : 0;
+
+ this.drawingArea = largestPossibleRadius - (radiusReductionLeft + radiusReductionRight)/2;
+
+ //this.drawingArea = min([maxWidthRadius, (this.height - (2 * (this.pointLabelFontSize + 5)))/2])
+ this.setCenterPoint(radiusReductionLeft, radiusReductionRight);
+
+ },
+ setCenterPoint: function(leftMovement, rightMovement){
+
+ var maxRight = this.width - rightMovement - this.drawingArea,
+ maxLeft = leftMovement + this.drawingArea;
+
+ this.xCenter = (maxLeft + maxRight)/2;
+ // Always vertically in the centre as the text height doesn't change
+ this.yCenter = (this.height/2);
+ },
+
+ getIndexAngle : function(index){
+ var angleMultiplier = (Math.PI * 2) / this.valuesCount;
+ // Start from the top instead of right, so remove a quarter of the circle
+
+ return index * angleMultiplier - (Math.PI/2);
+ },
+ getPointPosition : function(index, distanceFromCenter){
+ var thisAngle = this.getIndexAngle(index);
+ return {
+ x : (Math.cos(thisAngle) * distanceFromCenter) + this.xCenter,
+ y : (Math.sin(thisAngle) * distanceFromCenter) + this.yCenter
+ };
+ },
+ draw: function(){
+ if (this.display){
+ var ctx = this.ctx;
+ each(this.yLabels, function(label, index){
+ // Don't draw a centre value
+ if (index > 0){
+ var yCenterOffset = index * (this.drawingArea/this.steps),
+ yHeight = this.yCenter - yCenterOffset,
+ pointPosition;
+
+ // Draw circular lines around the scale
+ if (this.lineWidth > 0){
+ ctx.strokeStyle = this.lineColor;
+ ctx.lineWidth = this.lineWidth;
+
+ if(this.lineArc){
+ ctx.beginPath();
+ ctx.arc(this.xCenter, this.yCenter, yCenterOffset, 0, Math.PI*2);
+ ctx.closePath();
+ ctx.stroke();
+ } else{
+ ctx.beginPath();
+ for (var i=0;i= 0; i--) {
+ if (this.angleLineWidth > 0){
+ var outerPosition = this.getPointPosition(i, this.calculateCenterOffset(this.max));
+ ctx.beginPath();
+ ctx.moveTo(this.xCenter, this.yCenter);
+ ctx.lineTo(outerPosition.x, outerPosition.y);
+ ctx.stroke();
+ ctx.closePath();
+ }
+ // Extra 3px out for some label spacing
+ var pointLabelPosition = this.getPointPosition(i, this.calculateCenterOffset(this.max) + 5);
+ ctx.font = fontString(this.pointLabelFontSize,this.pointLabelFontStyle,this.pointLabelFontFamily);
+ ctx.fillStyle = this.pointLabelFontColor;
+
+ var labelsCount = this.labels.length,
+ halfLabelsCount = this.labels.length/2,
+ quarterLabelsCount = halfLabelsCount/2,
+ upperHalf = (i < quarterLabelsCount || i > labelsCount - quarterLabelsCount),
+ exactQuarter = (i === quarterLabelsCount || i === labelsCount - quarterLabelsCount);
+ if (i === 0){
+ ctx.textAlign = 'center';
+ } else if(i === halfLabelsCount){
+ ctx.textAlign = 'center';
+ } else if (i < halfLabelsCount){
+ ctx.textAlign = 'left';
+ } else {
+ ctx.textAlign = 'right';
+ }
+
+ // Set the correct text baseline based on outer positioning
+ if (exactQuarter){
+ ctx.textBaseline = 'middle';
+ } else if (upperHalf){
+ ctx.textBaseline = 'bottom';
+ } else {
+ ctx.textBaseline = 'top';
+ }
+
+ ctx.fillText(this.labels[i], pointLabelPosition.x, pointLabelPosition.y);
+ }
+ }
+ }
+ }
+ });
+
+ // Attach global event to resize each chart instance when the browser resizes
+ helpers.addEvent(window, "resize", (function(){
+ // Basic debounce of resize function so it doesn't hurt performance when resizing browser.
+ var timeout;
+ return function(){
+ clearTimeout(timeout);
+ timeout = setTimeout(function(){
+ each(Chart.instances,function(instance){
+ // If the responsive flag is set in the chart instance config
+ // Cascade the resize event down to the chart.
+ if (instance.options.responsive){
+ instance.resize(instance.render, true);
+ }
+ });
+ }, 50);
+ };
+ })());
+
+
+ if (amd) {
+ define(function(){
+ return Chart;
+ });
+ } else if (typeof module === 'object' && module.exports) {
+ module.exports = Chart;
+ }
+
+ root.Chart = Chart;
+
+ Chart.noConflict = function(){
+ root.Chart = previous;
+ return Chart;
+ };
+
+}).call(this);
+
+(function(){
+ "use strict";
+
+ var root = this,
+ Chart = root.Chart,
+ helpers = Chart.helpers;
+
+
+ var defaultConfig = {
+ //Boolean - Whether the scale should start at zero, or an order of magnitude down from the lowest value
+ scaleBeginAtZero : true,
+
+ //Boolean - Whether grid lines are shown across the chart
+ scaleShowGridLines : true,
+
+ //String - Colour of the grid lines
+ scaleGridLineColor : "rgba(0,0,0,.05)",
+
+ //Number - Width of the grid lines
+ scaleGridLineWidth : 1,
+
+ //Boolean - Whether to show horizontal lines (except X axis)
+ scaleShowHorizontalLines: true,
+
+ //Boolean - Whether to show vertical lines (except Y axis)
+ scaleShowVerticalLines: true,
+
+ //Boolean - If there is a stroke on each bar
+ barShowStroke : true,
+
+ //Number - Pixel width of the bar stroke
+ barStrokeWidth : 2,
+
+ //Number - Spacing between each of the X value sets
+ barValueSpacing : 5,
+
+ //Number - Spacing between data sets within X values
+ barDatasetSpacing : 1,
+
+ //String - A legend template
+ legendTemplate : "-legend\"><% for (var i=0; i\"> <%if(datasets[i].label){%><%=datasets[i].label%><%}%> <%}%> "
+
+ };
+
+
+ Chart.Type.extend({
+ name: "Bar",
+ defaults : defaultConfig,
+ initialize: function(data){
+
+ //Expose options as a scope variable here so we can access it in the ScaleClass
+ var options = this.options;
+
+ this.ScaleClass = Chart.Scale.extend({
+ offsetGridLines : true,
+ calculateBarX : function(datasetCount, datasetIndex, barIndex){
+ //Reusable method for calculating the xPosition of a given bar based on datasetIndex & width of the bar
+ var xWidth = this.calculateBaseWidth(),
+ xAbsolute = this.calculateX(barIndex) - (xWidth/2),
+ barWidth = this.calculateBarWidth(datasetCount);
+
+ return xAbsolute + (barWidth * datasetIndex) + (datasetIndex * options.barDatasetSpacing) + barWidth/2;
+ },
+ calculateBaseWidth : function(){
+ return (this.calculateX(1) - this.calculateX(0)) - (2*options.barValueSpacing);
+ },
+ calculateBarWidth : function(datasetCount){
+ //The padding between datasets is to the right of each bar, providing that there are more than 1 dataset
+ var baseWidth = this.calculateBaseWidth() - ((datasetCount - 1) * options.barDatasetSpacing);
+
+ return (baseWidth / datasetCount);
+ }
+ });
+
+ this.datasets = [];
+
+ //Set up tooltip events on the chart
+ if (this.options.showTooltips){
+ helpers.bindEvents(this, this.options.tooltipEvents, function(evt){
+ var activeBars = (evt.type !== 'mouseout') ? this.getBarsAtEvent(evt) : [];
+
+ this.eachBars(function(bar){
+ bar.restore(['fillColor', 'strokeColor']);
+ });
+ helpers.each(activeBars, function(activeBar){
+ activeBar.fillColor = activeBar.highlightFill;
+ activeBar.strokeColor = activeBar.highlightStroke;
+ });
+ this.showTooltip(activeBars);
+ });
+ }
+
+ //Declare the extension of the default point, to cater for the options passed in to the constructor
+ this.BarClass = Chart.Rectangle.extend({
+ strokeWidth : this.options.barStrokeWidth,
+ showStroke : this.options.barShowStroke,
+ ctx : this.chart.ctx
+ });
+
+ //Iterate through each of the datasets, and build this into a property of the chart
+ helpers.each(data.datasets,function(dataset,datasetIndex){
+
+ var datasetObject = {
+ label : dataset.label || null,
+ fillColor : dataset.fillColor,
+ strokeColor : dataset.strokeColor,
+ bars : []
+ };
+
+ this.datasets.push(datasetObject);
+
+ helpers.each(dataset.data,function(dataPoint,index){
+ //Add a new point for each piece of data, passing any required data to draw.
+ datasetObject.bars.push(new this.BarClass({
+ value : dataPoint,
+ label : data.labels[index],
+ datasetLabel: dataset.label,
+ strokeColor : dataset.strokeColor,
+ fillColor : dataset.fillColor,
+ highlightFill : dataset.highlightFill || dataset.fillColor,
+ highlightStroke : dataset.highlightStroke || dataset.strokeColor
+ }));
+ },this);
+
+ },this);
+
+ this.buildScale(data.labels);
+
+ this.BarClass.prototype.base = this.scale.endPoint;
+
+ this.eachBars(function(bar, index, datasetIndex){
+ helpers.extend(bar, {
+ width : this.scale.calculateBarWidth(this.datasets.length),
+ x: this.scale.calculateBarX(this.datasets.length, datasetIndex, index),
+ y: this.scale.endPoint
+ });
+ bar.save();
+ }, this);
+
+ this.render();
+ },
+ update : function(){
+ this.scale.update();
+ // Reset any highlight colours before updating.
+ helpers.each(this.activeElements, function(activeElement){
+ activeElement.restore(['fillColor', 'strokeColor']);
+ });
+
+ this.eachBars(function(bar){
+ bar.save();
+ });
+ this.render();
+ },
+ eachBars : function(callback){
+ helpers.each(this.datasets,function(dataset, datasetIndex){
+ helpers.each(dataset.bars, callback, this, datasetIndex);
+ },this);
+ },
+ getBarsAtEvent : function(e){
+ var barsArray = [],
+ eventPosition = helpers.getRelativePosition(e),
+ datasetIterator = function(dataset){
+ barsArray.push(dataset.bars[barIndex]);
+ },
+ barIndex;
+
+ for (var datasetIndex = 0; datasetIndex < this.datasets.length; datasetIndex++) {
+ for (barIndex = 0; barIndex < this.datasets[datasetIndex].bars.length; barIndex++) {
+ if (this.datasets[datasetIndex].bars[barIndex].inRange(eventPosition.x,eventPosition.y)){
+ helpers.each(this.datasets, datasetIterator);
+ return barsArray;
+ }
+ }
+ }
+
+ return barsArray;
+ },
+ buildScale : function(labels){
+ var self = this;
+
+ var dataTotal = function(){
+ var values = [];
+ self.eachBars(function(bar){
+ values.push(bar.value);
+ });
+ return values;
+ };
+
+ var scaleOptions = {
+ templateString : this.options.scaleLabel,
+ height : this.chart.height,
+ width : this.chart.width,
+ ctx : this.chart.ctx,
+ textColor : this.options.scaleFontColor,
+ fontSize : this.options.scaleFontSize,
+ fontStyle : this.options.scaleFontStyle,
+ fontFamily : this.options.scaleFontFamily,
+ valuesCount : labels.length,
+ beginAtZero : this.options.scaleBeginAtZero,
+ integersOnly : this.options.scaleIntegersOnly,
+ calculateYRange: function(currentHeight){
+ var updatedRanges = helpers.calculateScaleRange(
+ dataTotal(),
+ currentHeight,
+ this.fontSize,
+ this.beginAtZero,
+ this.integersOnly
+ );
+ helpers.extend(this, updatedRanges);
+ },
+ xLabels : labels,
+ font : helpers.fontString(this.options.scaleFontSize, this.options.scaleFontStyle, this.options.scaleFontFamily),
+ lineWidth : this.options.scaleLineWidth,
+ lineColor : this.options.scaleLineColor,
+ showHorizontalLines : this.options.scaleShowHorizontalLines,
+ showVerticalLines : this.options.scaleShowVerticalLines,
+ gridLineWidth : (this.options.scaleShowGridLines) ? this.options.scaleGridLineWidth : 0,
+ gridLineColor : (this.options.scaleShowGridLines) ? this.options.scaleGridLineColor : "rgba(0,0,0,0)",
+ padding : (this.options.showScale) ? 0 : (this.options.barShowStroke) ? this.options.barStrokeWidth : 0,
+ showLabels : this.options.scaleShowLabels,
+ display : this.options.showScale
+ };
+
+ if (this.options.scaleOverride){
+ helpers.extend(scaleOptions, {
+ calculateYRange: helpers.noop,
+ steps: this.options.scaleSteps,
+ stepValue: this.options.scaleStepWidth,
+ min: this.options.scaleStartValue,
+ max: this.options.scaleStartValue + (this.options.scaleSteps * this.options.scaleStepWidth)
+ });
+ }
+
+ this.scale = new this.ScaleClass(scaleOptions);
+ },
+ addData : function(valuesArray,label){
+ //Map the values array for each of the datasets
+ helpers.each(valuesArray,function(value,datasetIndex){
+ //Add a new point for each piece of data, passing any required data to draw.
+ this.datasets[datasetIndex].bars.push(new this.BarClass({
+ value : value,
+ label : label,
+ x: this.scale.calculateBarX(this.datasets.length, datasetIndex, this.scale.valuesCount+1),
+ y: this.scale.endPoint,
+ width : this.scale.calculateBarWidth(this.datasets.length),
+ base : this.scale.endPoint,
+ strokeColor : this.datasets[datasetIndex].strokeColor,
+ fillColor : this.datasets[datasetIndex].fillColor
+ }));
+ },this);
+
+ this.scale.addXLabel(label);
+ //Then re-render the chart.
+ this.update();
+ },
+ removeData : function(){
+ this.scale.removeXLabel();
+ //Then re-render the chart.
+ helpers.each(this.datasets,function(dataset){
+ dataset.bars.shift();
+ },this);
+ this.update();
+ },
+ reflow : function(){
+ helpers.extend(this.BarClass.prototype,{
+ y: this.scale.endPoint,
+ base : this.scale.endPoint
+ });
+ var newScaleProps = helpers.extend({
+ height : this.chart.height,
+ width : this.chart.width
+ });
+ this.scale.update(newScaleProps);
+ },
+ draw : function(ease){
+ var easingDecimal = ease || 1;
+ this.clear();
+
+ var ctx = this.chart.ctx;
+
+ this.scale.draw(easingDecimal);
+
+ //Draw all the bars for each dataset
+ helpers.each(this.datasets,function(dataset,datasetIndex){
+ helpers.each(dataset.bars,function(bar,index){
+ if (bar.hasValue()){
+ bar.base = this.scale.endPoint;
+ //Transition then draw
+ bar.transition({
+ x : this.scale.calculateBarX(this.datasets.length, datasetIndex, index),
+ y : this.scale.calculateY(bar.value),
+ width : this.scale.calculateBarWidth(this.datasets.length)
+ }, easingDecimal).draw();
+ }
+ },this);
+
+ },this);
+ }
+ });
+
+
+}).call(this);
+
+(function(){
+ "use strict";
+
+ var root = this,
+ Chart = root.Chart,
+ //Cache a local reference to Chart.helpers
+ helpers = Chart.helpers;
+
+ var defaultConfig = {
+ //Boolean - Whether we should show a stroke on each segment
+ segmentShowStroke : true,
+
+ //String - The colour of each segment stroke
+ segmentStrokeColor : "#fff",
+
+ //Number - The width of each segment stroke
+ segmentStrokeWidth : 2,
+
+ //The percentage of the chart that we cut out of the middle.
+ percentageInnerCutout : 50,
+
+ //Number - Amount of animation steps
+ animationSteps : 100,
+
+ //String - Animation easing effect
+ animationEasing : "easeOutBounce",
+
+ //Boolean - Whether we animate the rotation of the Doughnut
+ animateRotate : true,
+
+ //Boolean - Whether we animate scaling the Doughnut from the centre
+ animateScale : false,
+
+ //String - A legend template
+ legendTemplate : "-legend\"><% for (var i=0; i\"> <%if(segments[i].label){%><%=segments[i].label%><%}%> <%}%> "
+
+ };
+
+
+ Chart.Type.extend({
+ //Passing in a name registers this chart in the Chart namespace
+ name: "Doughnut",
+ //Providing a defaults will also register the deafults in the chart namespace
+ defaults : defaultConfig,
+ //Initialize is fired when the chart is initialized - Data is passed in as a parameter
+ //Config is automatically merged by the core of Chart.js, and is available at this.options
+ initialize: function(data){
+
+ //Declare segments as a static property to prevent inheriting across the Chart type prototype
+ this.segments = [];
+ this.outerRadius = (helpers.min([this.chart.width,this.chart.height]) - this.options.segmentStrokeWidth/2)/2;
+
+ this.SegmentArc = Chart.Arc.extend({
+ ctx : this.chart.ctx,
+ x : this.chart.width/2,
+ y : this.chart.height/2
+ });
+
+ //Set up tooltip events on the chart
+ if (this.options.showTooltips){
+ helpers.bindEvents(this, this.options.tooltipEvents, function(evt){
+ var activeSegments = (evt.type !== 'mouseout') ? this.getSegmentsAtEvent(evt) : [];
+
+ helpers.each(this.segments,function(segment){
+ segment.restore(["fillColor"]);
+ });
+ helpers.each(activeSegments,function(activeSegment){
+ activeSegment.fillColor = activeSegment.highlightColor;
+ });
+ this.showTooltip(activeSegments);
+ });
+ }
+ this.calculateTotal(data);
+
+ helpers.each(data,function(datapoint, index){
+ this.addData(datapoint, index, true);
+ },this);
+
+ this.render();
+ },
+ getSegmentsAtEvent : function(e){
+ var segmentsArray = [];
+
+ var location = helpers.getRelativePosition(e);
+
+ helpers.each(this.segments,function(segment){
+ if (segment.inRange(location.x,location.y)) segmentsArray.push(segment);
+ },this);
+ return segmentsArray;
+ },
+ addData : function(segment, atIndex, silent){
+ var index = atIndex || this.segments.length;
+ this.segments.splice(index, 0, new this.SegmentArc({
+ value : segment.value,
+ outerRadius : (this.options.animateScale) ? 0 : this.outerRadius,
+ innerRadius : (this.options.animateScale) ? 0 : (this.outerRadius/100) * this.options.percentageInnerCutout,
+ fillColor : segment.color,
+ highlightColor : segment.highlight || segment.color,
+ showStroke : this.options.segmentShowStroke,
+ strokeWidth : this.options.segmentStrokeWidth,
+ strokeColor : this.options.segmentStrokeColor,
+ startAngle : Math.PI * 1.5,
+ circumference : (this.options.animateRotate) ? 0 : this.calculateCircumference(segment.value),
+ label : segment.label
+ }));
+ if (!silent){
+ this.reflow();
+ this.update();
+ }
+ },
+ calculateCircumference : function(value){
+ return (Math.PI*2)*(Math.abs(value) / this.total);
+ },
+ calculateTotal : function(data){
+ this.total = 0;
+ helpers.each(data,function(segment){
+ this.total += Math.abs(segment.value);
+ },this);
+ },
+ update : function(){
+ this.calculateTotal(this.segments);
+
+ // Reset any highlight colours before updating.
+ helpers.each(this.activeElements, function(activeElement){
+ activeElement.restore(['fillColor']);
+ });
+
+ helpers.each(this.segments,function(segment){
+ segment.save();
+ });
+ this.render();
+ },
+
+ removeData: function(atIndex){
+ var indexToDelete = (helpers.isNumber(atIndex)) ? atIndex : this.segments.length-1;
+ this.segments.splice(indexToDelete, 1);
+ this.reflow();
+ this.update();
+ },
+
+ reflow : function(){
+ helpers.extend(this.SegmentArc.prototype,{
+ x : this.chart.width/2,
+ y : this.chart.height/2
+ });
+ this.outerRadius = (helpers.min([this.chart.width,this.chart.height]) - this.options.segmentStrokeWidth/2)/2;
+ helpers.each(this.segments, function(segment){
+ segment.update({
+ outerRadius : this.outerRadius,
+ innerRadius : (this.outerRadius/100) * this.options.percentageInnerCutout
+ });
+ }, this);
+ },
+ draw : function(easeDecimal){
+ var animDecimal = (easeDecimal) ? easeDecimal : 1;
+ this.clear();
+ helpers.each(this.segments,function(segment,index){
+ segment.transition({
+ circumference : this.calculateCircumference(segment.value),
+ outerRadius : this.outerRadius,
+ innerRadius : (this.outerRadius/100) * this.options.percentageInnerCutout
+ },animDecimal);
+
+ segment.endAngle = segment.startAngle + segment.circumference;
+
+ segment.draw();
+ if (index === 0){
+ segment.startAngle = Math.PI * 1.5;
+ }
+ //Check to see if it's the last segment, if not get the next and update the start angle
+ if (index < this.segments.length-1){
+ this.segments[index+1].startAngle = segment.endAngle;
+ }
+ },this);
+
+ }
+ });
+
+ Chart.types.Doughnut.extend({
+ name : "Pie",
+ defaults : helpers.merge(defaultConfig,{percentageInnerCutout : 0})
+ });
+
+}).call(this);
+(function(){
+ "use strict";
+
+ var root = this,
+ Chart = root.Chart,
+ helpers = Chart.helpers;
+
+ var defaultConfig = {
+
+ ///Boolean - Whether grid lines are shown across the chart
+ scaleShowGridLines : true,
+
+ //String - Colour of the grid lines
+ scaleGridLineColor : "rgba(0,0,0,.05)",
+
+ //Number - Width of the grid lines
+ scaleGridLineWidth : 1,
+
+ //Boolean - Whether to show horizontal lines (except X axis)
+ scaleShowHorizontalLines: true,
+
+ //Boolean - Whether to show vertical lines (except Y axis)
+ scaleShowVerticalLines: true,
+
+ //Boolean - Whether the line is curved between points
+ bezierCurve : true,
+
+ //Number - Tension of the bezier curve between points
+ bezierCurveTension : 0.4,
+
+ //Boolean - Whether to show a dot for each point
+ pointDot : true,
+
+ //Number - Radius of each point dot in pixels
+ pointDotRadius : 4,
+
+ //Number - Pixel width of point dot stroke
+ pointDotStrokeWidth : 1,
+
+ //Number - amount extra to add to the radius to cater for hit detection outside the drawn point
+ pointHitDetectionRadius : 20,
+
+ //Boolean - Whether to show a stroke for datasets
+ datasetStroke : true,
+
+ //Number - Pixel width of dataset stroke
+ datasetStrokeWidth : 2,
+
+ //Boolean - Whether to fill the dataset with a colour
+ datasetFill : true,
+
+ //String - A legend template
+ legendTemplate : "-legend\"><% for (var i=0; i\"> <%if(datasets[i].label){%><%=datasets[i].label%><%}%> <%}%> "
+
+ };
+
+
+ Chart.Type.extend({
+ name: "Line",
+ defaults : defaultConfig,
+ initialize: function(data){
+ //Declare the extension of the default point, to cater for the options passed in to the constructor
+ this.PointClass = Chart.Point.extend({
+ strokeWidth : this.options.pointDotStrokeWidth,
+ radius : this.options.pointDotRadius,
+ display: this.options.pointDot,
+ hitDetectionRadius : this.options.pointHitDetectionRadius,
+ ctx : this.chart.ctx,
+ inRange : function(mouseX){
+ return (Math.pow(mouseX-this.x, 2) < Math.pow(this.radius + this.hitDetectionRadius,2));
+ }
+ });
+
+ this.datasets = [];
+
+ //Set up tooltip events on the chart
+ if (this.options.showTooltips){
+ helpers.bindEvents(this, this.options.tooltipEvents, function(evt){
+ var activePoints = (evt.type !== 'mouseout') ? this.getPointsAtEvent(evt) : [];
+ this.eachPoints(function(point){
+ point.restore(['fillColor', 'strokeColor']);
+ });
+ helpers.each(activePoints, function(activePoint){
+ activePoint.fillColor = activePoint.highlightFill;
+ activePoint.strokeColor = activePoint.highlightStroke;
+ });
+ this.showTooltip(activePoints);
+ });
+ }
+
+ //Iterate through each of the datasets, and build this into a property of the chart
+ helpers.each(data.datasets,function(dataset){
+
+ var datasetObject = {
+ label : dataset.label || null,
+ fillColor : dataset.fillColor,
+ strokeColor : dataset.strokeColor,
+ pointColor : dataset.pointColor,
+ pointStrokeColor : dataset.pointStrokeColor,
+ points : []
+ };
+
+ this.datasets.push(datasetObject);
+
+
+ helpers.each(dataset.data,function(dataPoint,index){
+ //Add a new point for each piece of data, passing any required data to draw.
+ datasetObject.points.push(new this.PointClass({
+ value : dataPoint,
+ label : data.labels[index],
+ datasetLabel: dataset.label,
+ strokeColor : dataset.pointStrokeColor,
+ fillColor : dataset.pointColor,
+ highlightFill : dataset.pointHighlightFill || dataset.pointColor,
+ highlightStroke : dataset.pointHighlightStroke || dataset.pointStrokeColor
+ }));
+ },this);
+
+ this.buildScale(data.labels);
+
+
+ this.eachPoints(function(point, index){
+ helpers.extend(point, {
+ x: this.scale.calculateX(index),
+ y: this.scale.endPoint
+ });
+ point.save();
+ }, this);
+
+ },this);
+
+
+ this.render();
+ },
+ update : function(){
+ this.scale.update();
+ // Reset any highlight colours before updating.
+ helpers.each(this.activeElements, function(activeElement){
+ activeElement.restore(['fillColor', 'strokeColor']);
+ });
+ this.eachPoints(function(point){
+ point.save();
+ });
+ this.render();
+ },
+ eachPoints : function(callback){
+ helpers.each(this.datasets,function(dataset){
+ helpers.each(dataset.points,callback,this);
+ },this);
+ },
+ getPointsAtEvent : function(e){
+ var pointsArray = [],
+ eventPosition = helpers.getRelativePosition(e);
+ helpers.each(this.datasets,function(dataset){
+ helpers.each(dataset.points,function(point){
+ if (point.inRange(eventPosition.x,eventPosition.y)) pointsArray.push(point);
+ });
+ },this);
+ return pointsArray;
+ },
+ buildScale : function(labels){
+ var self = this;
+
+ var dataTotal = function(){
+ var values = [];
+ self.eachPoints(function(point){
+ values.push(point.value);
+ });
+
+ return values;
+ };
+
+ var scaleOptions = {
+ templateString : this.options.scaleLabel,
+ height : this.chart.height,
+ width : this.chart.width,
+ ctx : this.chart.ctx,
+ textColor : this.options.scaleFontColor,
+ fontSize : this.options.scaleFontSize,
+ fontStyle : this.options.scaleFontStyle,
+ fontFamily : this.options.scaleFontFamily,
+ valuesCount : labels.length,
+ beginAtZero : this.options.scaleBeginAtZero,
+ integersOnly : this.options.scaleIntegersOnly,
+ calculateYRange : function(currentHeight){
+ var updatedRanges = helpers.calculateScaleRange(
+ dataTotal(),
+ currentHeight,
+ this.fontSize,
+ this.beginAtZero,
+ this.integersOnly
+ );
+ helpers.extend(this, updatedRanges);
+ },
+ xLabels : labels,
+ font : helpers.fontString(this.options.scaleFontSize, this.options.scaleFontStyle, this.options.scaleFontFamily),
+ lineWidth : this.options.scaleLineWidth,
+ lineColor : this.options.scaleLineColor,
+ showHorizontalLines : this.options.scaleShowHorizontalLines,
+ showVerticalLines : this.options.scaleShowVerticalLines,
+ gridLineWidth : (this.options.scaleShowGridLines) ? this.options.scaleGridLineWidth : 0,
+ gridLineColor : (this.options.scaleShowGridLines) ? this.options.scaleGridLineColor : "rgba(0,0,0,0)",
+ padding: (this.options.showScale) ? 0 : this.options.pointDotRadius + this.options.pointDotStrokeWidth,
+ showLabels : this.options.scaleShowLabels,
+ display : this.options.showScale
+ };
+
+ if (this.options.scaleOverride){
+ helpers.extend(scaleOptions, {
+ calculateYRange: helpers.noop,
+ steps: this.options.scaleSteps,
+ stepValue: this.options.scaleStepWidth,
+ min: this.options.scaleStartValue,
+ max: this.options.scaleStartValue + (this.options.scaleSteps * this.options.scaleStepWidth)
+ });
+ }
+
+
+ this.scale = new Chart.Scale(scaleOptions);
+ },
+ addData : function(valuesArray,label){
+ //Map the values array for each of the datasets
+
+ helpers.each(valuesArray,function(value,datasetIndex){
+ //Add a new point for each piece of data, passing any required data to draw.
+ this.datasets[datasetIndex].points.push(new this.PointClass({
+ value : value,
+ label : label,
+ x: this.scale.calculateX(this.scale.valuesCount+1),
+ y: this.scale.endPoint,
+ strokeColor : this.datasets[datasetIndex].pointStrokeColor,
+ fillColor : this.datasets[datasetIndex].pointColor
+ }));
+ },this);
+
+ this.scale.addXLabel(label);
+ //Then re-render the chart.
+ this.update();
+ },
+ removeData : function(){
+ this.scale.removeXLabel();
+ //Then re-render the chart.
+ helpers.each(this.datasets,function(dataset){
+ dataset.points.shift();
+ },this);
+ this.update();
+ },
+ reflow : function(){
+ var newScaleProps = helpers.extend({
+ height : this.chart.height,
+ width : this.chart.width
+ });
+ this.scale.update(newScaleProps);
+ },
+ draw : function(ease){
+ var easingDecimal = ease || 1;
+ this.clear();
+
+ var ctx = this.chart.ctx;
+
+ // Some helper methods for getting the next/prev points
+ var hasValue = function(item){
+ return item.value !== null;
+ },
+ nextPoint = function(point, collection, index){
+ return helpers.findNextWhere(collection, hasValue, index) || point;
+ },
+ previousPoint = function(point, collection, index){
+ return helpers.findPreviousWhere(collection, hasValue, index) || point;
+ };
+
+ this.scale.draw(easingDecimal);
+
+
+ helpers.each(this.datasets,function(dataset){
+ var pointsWithValues = helpers.where(dataset.points, hasValue);
+
+ //Transition each point first so that the line and point drawing isn't out of sync
+ //We can use this extra loop to calculate the control points of this dataset also in this loop
+
+ helpers.each(dataset.points, function(point, index){
+ if (point.hasValue()){
+ point.transition({
+ y : this.scale.calculateY(point.value),
+ x : this.scale.calculateX(index)
+ }, easingDecimal);
+ }
+ },this);
+
+
+ // Control points need to be calculated in a seperate loop, because we need to know the current x/y of the point
+ // This would cause issues when there is no animation, because the y of the next point would be 0, so beziers would be skewed
+ if (this.options.bezierCurve){
+ helpers.each(pointsWithValues, function(point, index){
+ var tension = (index > 0 && index < pointsWithValues.length - 1) ? this.options.bezierCurveTension : 0;
+ point.controlPoints = helpers.splineCurve(
+ previousPoint(point, pointsWithValues, index),
+ point,
+ nextPoint(point, pointsWithValues, index),
+ tension
+ );
+
+ // Prevent the bezier going outside of the bounds of the graph
+
+ // Cap puter bezier handles to the upper/lower scale bounds
+ if (point.controlPoints.outer.y > this.scale.endPoint){
+ point.controlPoints.outer.y = this.scale.endPoint;
+ }
+ else if (point.controlPoints.outer.y < this.scale.startPoint){
+ point.controlPoints.outer.y = this.scale.startPoint;
+ }
+
+ // Cap inner bezier handles to the upper/lower scale bounds
+ if (point.controlPoints.inner.y > this.scale.endPoint){
+ point.controlPoints.inner.y = this.scale.endPoint;
+ }
+ else if (point.controlPoints.inner.y < this.scale.startPoint){
+ point.controlPoints.inner.y = this.scale.startPoint;
+ }
+ },this);
+ }
+
+
+ //Draw the line between all the points
+ ctx.lineWidth = this.options.datasetStrokeWidth;
+ ctx.strokeStyle = dataset.strokeColor;
+ ctx.beginPath();
+
+ helpers.each(pointsWithValues, function(point, index){
+ if (index === 0){
+ ctx.moveTo(point.x, point.y);
+ }
+ else{
+ if(this.options.bezierCurve){
+ var previous = previousPoint(point, pointsWithValues, index);
+
+ ctx.bezierCurveTo(
+ previous.controlPoints.outer.x,
+ previous.controlPoints.outer.y,
+ point.controlPoints.inner.x,
+ point.controlPoints.inner.y,
+ point.x,
+ point.y
+ );
+ }
+ else{
+ ctx.lineTo(point.x,point.y);
+ }
+ }
+ }, this);
+
+ ctx.stroke();
+
+ if (this.options.datasetFill && pointsWithValues.length > 0){
+ //Round off the line by going to the base of the chart, back to the start, then fill.
+ ctx.lineTo(pointsWithValues[pointsWithValues.length - 1].x, this.scale.endPoint);
+ ctx.lineTo(pointsWithValues[0].x, this.scale.endPoint);
+ ctx.fillStyle = dataset.fillColor;
+ ctx.closePath();
+ ctx.fill();
+ }
+
+ //Now draw the points over the line
+ //A little inefficient double looping, but better than the line
+ //lagging behind the point positions
+ helpers.each(pointsWithValues,function(point){
+ point.draw();
+ });
+ },this);
+ }
+ });
+
+
+}).call(this);
+
+(function(){
+ "use strict";
+
+ var root = this,
+ Chart = root.Chart,
+ //Cache a local reference to Chart.helpers
+ helpers = Chart.helpers;
+
+ var defaultConfig = {
+ //Boolean - Show a backdrop to the scale label
+ scaleShowLabelBackdrop : true,
+
+ //String - The colour of the label backdrop
+ scaleBackdropColor : "rgba(255,255,255,0.75)",
+
+ // Boolean - Whether the scale should begin at zero
+ scaleBeginAtZero : true,
+
+ //Number - The backdrop padding above & below the label in pixels
+ scaleBackdropPaddingY : 2,
+
+ //Number - The backdrop padding to the side of the label in pixels
+ scaleBackdropPaddingX : 2,
+
+ //Boolean - Show line for each value in the scale
+ scaleShowLine : true,
+
+ //Boolean - Stroke a line around each segment in the chart
+ segmentShowStroke : true,
+
+ //String - The colour of the stroke on each segement.
+ segmentStrokeColor : "#fff",
+
+ //Number - The width of the stroke value in pixels
+ segmentStrokeWidth : 2,
+
+ //Number - Amount of animation steps
+ animationSteps : 100,
+
+ //String - Animation easing effect.
+ animationEasing : "easeOutBounce",
+
+ //Boolean - Whether to animate the rotation of the chart
+ animateRotate : true,
+
+ //Boolean - Whether to animate scaling the chart from the centre
+ animateScale : false,
+
+ //String - A legend template
+ legendTemplate : "-legend\"><% for (var i=0; i\"> <%if(segments[i].label){%><%=segments[i].label%><%}%> <%}%> "
+ };
+
+
+ Chart.Type.extend({
+ //Passing in a name registers this chart in the Chart namespace
+ name: "PolarArea",
+ //Providing a defaults will also register the deafults in the chart namespace
+ defaults : defaultConfig,
+ //Initialize is fired when the chart is initialized - Data is passed in as a parameter
+ //Config is automatically merged by the core of Chart.js, and is available at this.options
+ initialize: function(data){
+ this.segments = [];
+ //Declare segment class as a chart instance specific class, so it can share props for this instance
+ this.SegmentArc = Chart.Arc.extend({
+ showStroke : this.options.segmentShowStroke,
+ strokeWidth : this.options.segmentStrokeWidth,
+ strokeColor : this.options.segmentStrokeColor,
+ ctx : this.chart.ctx,
+ innerRadius : 0,
+ x : this.chart.width/2,
+ y : this.chart.height/2
+ });
+ this.scale = new Chart.RadialScale({
+ display: this.options.showScale,
+ fontStyle: this.options.scaleFontStyle,
+ fontSize: this.options.scaleFontSize,
+ fontFamily: this.options.scaleFontFamily,
+ fontColor: this.options.scaleFontColor,
+ showLabels: this.options.scaleShowLabels,
+ showLabelBackdrop: this.options.scaleShowLabelBackdrop,
+ backdropColor: this.options.scaleBackdropColor,
+ backdropPaddingY : this.options.scaleBackdropPaddingY,
+ backdropPaddingX: this.options.scaleBackdropPaddingX,
+ lineWidth: (this.options.scaleShowLine) ? this.options.scaleLineWidth : 0,
+ lineColor: this.options.scaleLineColor,
+ lineArc: true,
+ width: this.chart.width,
+ height: this.chart.height,
+ xCenter: this.chart.width/2,
+ yCenter: this.chart.height/2,
+ ctx : this.chart.ctx,
+ templateString: this.options.scaleLabel,
+ valuesCount: data.length
+ });
+
+ this.updateScaleRange(data);
+
+ this.scale.update();
+
+ helpers.each(data,function(segment,index){
+ this.addData(segment,index,true);
+ },this);
+
+ //Set up tooltip events on the chart
+ if (this.options.showTooltips){
+ helpers.bindEvents(this, this.options.tooltipEvents, function(evt){
+ var activeSegments = (evt.type !== 'mouseout') ? this.getSegmentsAtEvent(evt) : [];
+ helpers.each(this.segments,function(segment){
+ segment.restore(["fillColor"]);
+ });
+ helpers.each(activeSegments,function(activeSegment){
+ activeSegment.fillColor = activeSegment.highlightColor;
+ });
+ this.showTooltip(activeSegments);
+ });
+ }
+
+ this.render();
+ },
+ getSegmentsAtEvent : function(e){
+ var segmentsArray = [];
+
+ var location = helpers.getRelativePosition(e);
+
+ helpers.each(this.segments,function(segment){
+ if (segment.inRange(location.x,location.y)) segmentsArray.push(segment);
+ },this);
+ return segmentsArray;
+ },
+ addData : function(segment, atIndex, silent){
+ var index = atIndex || this.segments.length;
+
+ this.segments.splice(index, 0, new this.SegmentArc({
+ fillColor: segment.color,
+ highlightColor: segment.highlight || segment.color,
+ label: segment.label,
+ value: segment.value,
+ outerRadius: (this.options.animateScale) ? 0 : this.scale.calculateCenterOffset(segment.value),
+ circumference: (this.options.animateRotate) ? 0 : this.scale.getCircumference(),
+ startAngle: Math.PI * 1.5
+ }));
+ if (!silent){
+ this.reflow();
+ this.update();
+ }
+ },
+ removeData: function(atIndex){
+ var indexToDelete = (helpers.isNumber(atIndex)) ? atIndex : this.segments.length-1;
+ this.segments.splice(indexToDelete, 1);
+ this.reflow();
+ this.update();
+ },
+ calculateTotal: function(data){
+ this.total = 0;
+ helpers.each(data,function(segment){
+ this.total += segment.value;
+ },this);
+ this.scale.valuesCount = this.segments.length;
+ },
+ updateScaleRange: function(datapoints){
+ var valuesArray = [];
+ helpers.each(datapoints,function(segment){
+ valuesArray.push(segment.value);
+ });
+
+ var scaleSizes = (this.options.scaleOverride) ?
+ {
+ steps: this.options.scaleSteps,
+ stepValue: this.options.scaleStepWidth,
+ min: this.options.scaleStartValue,
+ max: this.options.scaleStartValue + (this.options.scaleSteps * this.options.scaleStepWidth)
+ } :
+ helpers.calculateScaleRange(
+ valuesArray,
+ helpers.min([this.chart.width, this.chart.height])/2,
+ this.options.scaleFontSize,
+ this.options.scaleBeginAtZero,
+ this.options.scaleIntegersOnly
+ );
+
+ helpers.extend(
+ this.scale,
+ scaleSizes,
+ {
+ size: helpers.min([this.chart.width, this.chart.height]),
+ xCenter: this.chart.width/2,
+ yCenter: this.chart.height/2
+ }
+ );
+
+ },
+ update : function(){
+ this.calculateTotal(this.segments);
+
+ helpers.each(this.segments,function(segment){
+ segment.save();
+ });
+
+ this.reflow();
+ this.render();
+ },
+ reflow : function(){
+ helpers.extend(this.SegmentArc.prototype,{
+ x : this.chart.width/2,
+ y : this.chart.height/2
+ });
+ this.updateScaleRange(this.segments);
+ this.scale.update();
+
+ helpers.extend(this.scale,{
+ xCenter: this.chart.width/2,
+ yCenter: this.chart.height/2
+ });
+
+ helpers.each(this.segments, function(segment){
+ segment.update({
+ outerRadius : this.scale.calculateCenterOffset(segment.value)
+ });
+ }, this);
+
+ },
+ draw : function(ease){
+ var easingDecimal = ease || 1;
+ //Clear & draw the canvas
+ this.clear();
+ helpers.each(this.segments,function(segment, index){
+ segment.transition({
+ circumference : this.scale.getCircumference(),
+ outerRadius : this.scale.calculateCenterOffset(segment.value)
+ },easingDecimal);
+
+ segment.endAngle = segment.startAngle + segment.circumference;
+
+ // If we've removed the first segment we need to set the first one to
+ // start at the top.
+ if (index === 0){
+ segment.startAngle = Math.PI * 1.5;
+ }
+
+ //Check to see if it's the last segment, if not get the next and update the start angle
+ if (index < this.segments.length - 1){
+ this.segments[index+1].startAngle = segment.endAngle;
+ }
+ segment.draw();
+ }, this);
+ this.scale.draw();
+ }
+ });
+
+}).call(this);
+(function(){
+ "use strict";
+
+ var root = this,
+ Chart = root.Chart,
+ helpers = Chart.helpers;
+
+
+
+ Chart.Type.extend({
+ name: "Radar",
+ defaults:{
+ //Boolean - Whether to show lines for each scale point
+ scaleShowLine : true,
+
+ //Boolean - Whether we show the angle lines out of the radar
+ angleShowLineOut : true,
+
+ //Boolean - Whether to show labels on the scale
+ scaleShowLabels : false,
+
+ // Boolean - Whether the scale should begin at zero
+ scaleBeginAtZero : true,
+
+ //String - Colour of the angle line
+ angleLineColor : "rgba(0,0,0,.1)",
+
+ //Number - Pixel width of the angle line
+ angleLineWidth : 1,
+
+ //String - Point label font declaration
+ pointLabelFontFamily : "'Arial'",
+
+ //String - Point label font weight
+ pointLabelFontStyle : "normal",
+
+ //Number - Point label font size in pixels
+ pointLabelFontSize : 10,
+
+ //String - Point label font colour
+ pointLabelFontColor : "#666",
+
+ //Boolean - Whether to show a dot for each point
+ pointDot : true,
+
+ //Number - Radius of each point dot in pixels
+ pointDotRadius : 3,
+
+ //Number - Pixel width of point dot stroke
+ pointDotStrokeWidth : 1,
+
+ //Number - amount extra to add to the radius to cater for hit detection outside the drawn point
+ pointHitDetectionRadius : 20,
+
+ //Boolean - Whether to show a stroke for datasets
+ datasetStroke : true,
+
+ //Number - Pixel width of dataset stroke
+ datasetStrokeWidth : 2,
+
+ //Boolean - Whether to fill the dataset with a colour
+ datasetFill : true,
+
+ //String - A legend template
+ legendTemplate : "-legend\"><% for (var i=0; i\"> <%if(datasets[i].label){%><%=datasets[i].label%><%}%> <%}%> "
+
+ },
+
+ initialize: function(data){
+ this.PointClass = Chart.Point.extend({
+ strokeWidth : this.options.pointDotStrokeWidth,
+ radius : this.options.pointDotRadius,
+ display: this.options.pointDot,
+ hitDetectionRadius : this.options.pointHitDetectionRadius,
+ ctx : this.chart.ctx
+ });
+
+ this.datasets = [];
+
+ this.buildScale(data);
+
+ //Set up tooltip events on the chart
+ if (this.options.showTooltips){
+ helpers.bindEvents(this, this.options.tooltipEvents, function(evt){
+ var activePointsCollection = (evt.type !== 'mouseout') ? this.getPointsAtEvent(evt) : [];
+
+ this.eachPoints(function(point){
+ point.restore(['fillColor', 'strokeColor']);
+ });
+ helpers.each(activePointsCollection, function(activePoint){
+ activePoint.fillColor = activePoint.highlightFill;
+ activePoint.strokeColor = activePoint.highlightStroke;
+ });
+
+ this.showTooltip(activePointsCollection);
+ });
+ }
+
+ //Iterate through each of the datasets, and build this into a property of the chart
+ helpers.each(data.datasets,function(dataset){
+
+ var datasetObject = {
+ label: dataset.label || null,
+ fillColor : dataset.fillColor,
+ strokeColor : dataset.strokeColor,
+ pointColor : dataset.pointColor,
+ pointStrokeColor : dataset.pointStrokeColor,
+ points : []
+ };
+
+ this.datasets.push(datasetObject);
+
+ helpers.each(dataset.data,function(dataPoint,index){
+ //Add a new point for each piece of data, passing any required data to draw.
+ var pointPosition;
+ if (!this.scale.animation){
+ pointPosition = this.scale.getPointPosition(index, this.scale.calculateCenterOffset(dataPoint));
+ }
+ datasetObject.points.push(new this.PointClass({
+ value : dataPoint,
+ label : data.labels[index],
+ datasetLabel: dataset.label,
+ x: (this.options.animation) ? this.scale.xCenter : pointPosition.x,
+ y: (this.options.animation) ? this.scale.yCenter : pointPosition.y,
+ strokeColor : dataset.pointStrokeColor,
+ fillColor : dataset.pointColor,
+ highlightFill : dataset.pointHighlightFill || dataset.pointColor,
+ highlightStroke : dataset.pointHighlightStroke || dataset.pointStrokeColor
+ }));
+ },this);
+
+ },this);
+
+ this.render();
+ },
+ eachPoints : function(callback){
+ helpers.each(this.datasets,function(dataset){
+ helpers.each(dataset.points,callback,this);
+ },this);
+ },
+
+ getPointsAtEvent : function(evt){
+ var mousePosition = helpers.getRelativePosition(evt),
+ fromCenter = helpers.getAngleFromPoint({
+ x: this.scale.xCenter,
+ y: this.scale.yCenter
+ }, mousePosition);
+
+ var anglePerIndex = (Math.PI * 2) /this.scale.valuesCount,
+ pointIndex = Math.round((fromCenter.angle - Math.PI * 1.5) / anglePerIndex),
+ activePointsCollection = [];
+
+ // If we're at the top, make the pointIndex 0 to get the first of the array.
+ if (pointIndex >= this.scale.valuesCount || pointIndex < 0){
+ pointIndex = 0;
+ }
+
+ if (fromCenter.distance <= this.scale.drawingArea){
+ helpers.each(this.datasets, function(dataset){
+ activePointsCollection.push(dataset.points[pointIndex]);
+ });
+ }
+
+ return activePointsCollection;
+ },
+
+ buildScale : function(data){
+ this.scale = new Chart.RadialScale({
+ display: this.options.showScale,
+ fontStyle: this.options.scaleFontStyle,
+ fontSize: this.options.scaleFontSize,
+ fontFamily: this.options.scaleFontFamily,
+ fontColor: this.options.scaleFontColor,
+ showLabels: this.options.scaleShowLabels,
+ showLabelBackdrop: this.options.scaleShowLabelBackdrop,
+ backdropColor: this.options.scaleBackdropColor,
+ backdropPaddingY : this.options.scaleBackdropPaddingY,
+ backdropPaddingX: this.options.scaleBackdropPaddingX,
+ lineWidth: (this.options.scaleShowLine) ? this.options.scaleLineWidth : 0,
+ lineColor: this.options.scaleLineColor,
+ angleLineColor : this.options.angleLineColor,
+ angleLineWidth : (this.options.angleShowLineOut) ? this.options.angleLineWidth : 0,
+ // Point labels at the edge of each line
+ pointLabelFontColor : this.options.pointLabelFontColor,
+ pointLabelFontSize : this.options.pointLabelFontSize,
+ pointLabelFontFamily : this.options.pointLabelFontFamily,
+ pointLabelFontStyle : this.options.pointLabelFontStyle,
+ height : this.chart.height,
+ width: this.chart.width,
+ xCenter: this.chart.width/2,
+ yCenter: this.chart.height/2,
+ ctx : this.chart.ctx,
+ templateString: this.options.scaleLabel,
+ labels: data.labels,
+ valuesCount: data.datasets[0].data.length
+ });
+
+ this.scale.setScaleSize();
+ this.updateScaleRange(data.datasets);
+ this.scale.buildYLabels();
+ },
+ updateScaleRange: function(datasets){
+ var valuesArray = (function(){
+ var totalDataArray = [];
+ helpers.each(datasets,function(dataset){
+ if (dataset.data){
+ totalDataArray = totalDataArray.concat(dataset.data);
+ }
+ else {
+ helpers.each(dataset.points, function(point){
+ totalDataArray.push(point.value);
+ });
+ }
+ });
+ return totalDataArray;
+ })();
+
+
+ var scaleSizes = (this.options.scaleOverride) ?
+ {
+ steps: this.options.scaleSteps,
+ stepValue: this.options.scaleStepWidth,
+ min: this.options.scaleStartValue,
+ max: this.options.scaleStartValue + (this.options.scaleSteps * this.options.scaleStepWidth)
+ } :
+ helpers.calculateScaleRange(
+ valuesArray,
+ helpers.min([this.chart.width, this.chart.height])/2,
+ this.options.scaleFontSize,
+ this.options.scaleBeginAtZero,
+ this.options.scaleIntegersOnly
+ );
+
+ helpers.extend(
+ this.scale,
+ scaleSizes
+ );
+
+ },
+ addData : function(valuesArray,label){
+ //Map the values array for each of the datasets
+ this.scale.valuesCount++;
+ helpers.each(valuesArray,function(value,datasetIndex){
+ var pointPosition = this.scale.getPointPosition(this.scale.valuesCount, this.scale.calculateCenterOffset(value));
+ this.datasets[datasetIndex].points.push(new this.PointClass({
+ value : value,
+ label : label,
+ x: pointPosition.x,
+ y: pointPosition.y,
+ strokeColor : this.datasets[datasetIndex].pointStrokeColor,
+ fillColor : this.datasets[datasetIndex].pointColor
+ }));
+ },this);
+
+ this.scale.labels.push(label);
+
+ this.reflow();
+
+ this.update();
+ },
+ removeData : function(){
+ this.scale.valuesCount--;
+ this.scale.labels.shift();
+ helpers.each(this.datasets,function(dataset){
+ dataset.points.shift();
+ },this);
+ this.reflow();
+ this.update();
+ },
+ update : function(){
+ this.eachPoints(function(point){
+ point.save();
+ });
+ this.reflow();
+ this.render();
+ },
+ reflow: function(){
+ helpers.extend(this.scale, {
+ width : this.chart.width,
+ height: this.chart.height,
+ size : helpers.min([this.chart.width, this.chart.height]),
+ xCenter: this.chart.width/2,
+ yCenter: this.chart.height/2
+ });
+ this.updateScaleRange(this.datasets);
+ this.scale.setScaleSize();
+ this.scale.buildYLabels();
+ },
+ draw : function(ease){
+ var easeDecimal = ease || 1,
+ ctx = this.chart.ctx;
+ this.clear();
+ this.scale.draw();
+
+ helpers.each(this.datasets,function(dataset){
+
+ //Transition each point first so that the line and point drawing isn't out of sync
+ helpers.each(dataset.points,function(point,index){
+ if (point.hasValue()){
+ point.transition(this.scale.getPointPosition(index, this.scale.calculateCenterOffset(point.value)), easeDecimal);
+ }
+ },this);
+
+
+
+ //Draw the line between all the points
+ ctx.lineWidth = this.options.datasetStrokeWidth;
+ ctx.strokeStyle = dataset.strokeColor;
+ ctx.beginPath();
+ helpers.each(dataset.points,function(point,index){
+ if (index === 0){
+ ctx.moveTo(point.x,point.y);
+ }
+ else{
+ ctx.lineTo(point.x,point.y);
+ }
+ },this);
+ ctx.closePath();
+ ctx.stroke();
+
+ ctx.fillStyle = dataset.fillColor;
+ ctx.fill();
+
+ //Now draw the points over the line
+ //A little inefficient double looping, but better than the line
+ //lagging behind the point positions
+ helpers.each(dataset.points,function(point){
+ if (point.hasValue()){
+ point.draw();
+ }
+ });
+
+ },this);
+
+ }
+
+ });
+
+
+
+
+
+}).call(this);
\ No newline at end of file
diff --git a/vendor/assets/javascripts/fuzzaldrin-plus.js b/vendor/assets/javascripts/fuzzaldrin-plus.js
new file mode 100644
index 0000000000..1985e3f8f6
--- /dev/null
+++ b/vendor/assets/javascripts/fuzzaldrin-plus.js
@@ -0,0 +1,1161 @@
+/*!
+ * fuzzaldrin-plus.js - 0.3.1
+ * https://github.com/jeancroy/fuzzaldrin-plus
+ *
+ * Copyright 2016 - Jean Christophe Roy
+ * Released under the MIT license
+ * https://github.com/jeancroy/fuzzaldrin-plus/raw/master/LICENSE.md
+ */
+(function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o 0 ? maxInners : candidates.length;
+ bAllowErrors = !!allowErrors;
+ bKey = key != null;
+ prepQuery = scorer.prepQuery(query);
+ if (!legacy) {
+ for (_i = 0, _len = candidates.length; _i < _len; _i++) {
+ candidate = candidates[_i];
+ string = bKey ? candidate[key] : candidate;
+ if (!string) {
+ continue;
+ }
+ score = scorer.score(string, query, prepQuery, bAllowErrors);
+ if (score > 0) {
+ scoredCandidates.push({
+ candidate: candidate,
+ score: score
+ });
+ if (!--spotLeft) {
+ break;
+ }
+ }
+ }
+ } else {
+ queryHasSlashes = prepQuery.depth > 0;
+ coreQuery = prepQuery.core;
+ for (_j = 0, _len1 = candidates.length; _j < _len1; _j++) {
+ candidate = candidates[_j];
+ string = key != null ? candidate[key] : candidate;
+ if (!string) {
+ continue;
+ }
+ score = legacy_scorer.score(string, coreQuery, queryHasSlashes);
+ if (!queryHasSlashes) {
+ score = legacy_scorer.basenameScore(string, coreQuery, score);
+ }
+ if (score > 0) {
+ scoredCandidates.push({
+ candidate: candidate,
+ score: score
+ });
+ }
+ }
+ }
+ scoredCandidates.sort(sortCandidates);
+ candidates = scoredCandidates.map(pluckCandidates);
+ if (maxResults != null) {
+ candidates = candidates.slice(0, maxResults);
+ }
+ return candidates;
+ };
+
+}).call(this);
+
+},{"./legacy":4,"./scorer":6,"path":7}],3:[function(require,module,exports){
+(function() {
+ var PathSeparator, filter, legacy_scorer, matcher, prepQueryCache, scorer;
+
+ scorer = require('./scorer');
+
+ legacy_scorer = require('./legacy');
+
+ filter = require('./filter');
+
+ matcher = require('./matcher');
+
+ PathSeparator = require('path').sep;
+
+ prepQueryCache = null;
+
+ module.exports = {
+ filter: function(candidates, query, options) {
+ if (!((query != null ? query.length : void 0) && (candidates != null ? candidates.length : void 0))) {
+ return [];
+ }
+ return filter(candidates, query, options);
+ },
+ prepQuery: function(query) {
+ return scorer.prepQuery(query);
+ },
+ score: function(string, query, prepQuery, _arg) {
+ var allowErrors, coreQuery, legacy, queryHasSlashes, score, _ref;
+ _ref = _arg != null ? _arg : {}, allowErrors = _ref.allowErrors, legacy = _ref.legacy;
+ if (!((string != null ? string.length : void 0) && (query != null ? query.length : void 0))) {
+ return 0;
+ }
+ if (prepQuery == null) {
+ prepQuery = prepQueryCache && prepQueryCache.query === query ? prepQueryCache : (prepQueryCache = scorer.prepQuery(query));
+ }
+ if (!legacy) {
+ score = scorer.score(string, query, prepQuery, !!allowErrors);
+ } else {
+ queryHasSlashes = prepQuery.depth > 0;
+ coreQuery = prepQuery.core;
+ score = legacy_scorer.score(string, coreQuery, queryHasSlashes);
+ if (!queryHasSlashes) {
+ score = legacy_scorer.basenameScore(string, coreQuery, score);
+ }
+ }
+ return score;
+ },
+ match: function(string, query, prepQuery, _arg) {
+ var allowErrors, baseMatches, matches, query_lw, string_lw, _i, _ref, _results;
+ allowErrors = (_arg != null ? _arg : {}).allowErrors;
+ if (!string) {
+ return [];
+ }
+ if (!query) {
+ return [];
+ }
+ if (string === query) {
+ return (function() {
+ _results = [];
+ for (var _i = 0, _ref = string.length; 0 <= _ref ? _i < _ref : _i > _ref; 0 <= _ref ? _i++ : _i--){ _results.push(_i); }
+ return _results;
+ }).apply(this);
+ }
+ if (prepQuery == null) {
+ prepQuery = prepQueryCache && prepQueryCache.query === query ? prepQueryCache : (prepQueryCache = scorer.prepQuery(query));
+ }
+ if (!(allowErrors || scorer.isMatch(string, prepQuery.core_lw, prepQuery.core_up))) {
+ return [];
+ }
+ string_lw = string.toLowerCase();
+ query_lw = prepQuery.query_lw;
+ matches = matcher.match(string, string_lw, prepQuery);
+ if (matches.length === 0) {
+ return matches;
+ }
+ if (string.indexOf(PathSeparator) > -1) {
+ baseMatches = matcher.basenameMatch(string, string_lw, prepQuery);
+ matches = matcher.mergeMatches(matches, baseMatches);
+ }
+ return matches;
+ }
+ };
+
+}).call(this);
+
+},{"./filter":2,"./legacy":4,"./matcher":5,"./scorer":6,"path":7}],4:[function(require,module,exports){
+(function() {
+ var PathSeparator, queryIsLastPathSegment;
+
+ PathSeparator = require('path').sep;
+
+ exports.basenameScore = function(string, query, score) {
+ var base, depth, index, lastCharacter, segmentCount, slashCount;
+ index = string.length - 1;
+ while (string[index] === PathSeparator) {
+ index--;
+ }
+ slashCount = 0;
+ lastCharacter = index;
+ base = null;
+ while (index >= 0) {
+ if (string[index] === PathSeparator) {
+ slashCount++;
+ if (base == null) {
+ base = string.substring(index + 1, lastCharacter + 1);
+ }
+ } else if (index === 0) {
+ if (lastCharacter < string.length - 1) {
+ if (base == null) {
+ base = string.substring(0, lastCharacter + 1);
+ }
+ } else {
+ if (base == null) {
+ base = string;
+ }
+ }
+ }
+ index--;
+ }
+ if (base === string) {
+ score *= 2;
+ } else if (base) {
+ score += exports.score(base, query);
+ }
+ segmentCount = slashCount + 1;
+ depth = Math.max(1, 10 - segmentCount);
+ score *= depth * 0.01;
+ return score;
+ };
+
+ exports.score = function(string, query) {
+ var character, characterScore, indexInQuery, indexInString, lowerCaseIndex, minIndex, queryLength, queryScore, stringLength, totalCharacterScore, upperCaseIndex, _ref;
+ if (string === query) {
+ return 1;
+ }
+ if (queryIsLastPathSegment(string, query)) {
+ return 1;
+ }
+ totalCharacterScore = 0;
+ queryLength = query.length;
+ stringLength = string.length;
+ indexInQuery = 0;
+ indexInString = 0;
+ while (indexInQuery < queryLength) {
+ character = query[indexInQuery++];
+ lowerCaseIndex = string.indexOf(character.toLowerCase());
+ upperCaseIndex = string.indexOf(character.toUpperCase());
+ minIndex = Math.min(lowerCaseIndex, upperCaseIndex);
+ if (minIndex === -1) {
+ minIndex = Math.max(lowerCaseIndex, upperCaseIndex);
+ }
+ indexInString = minIndex;
+ if (indexInString === -1) {
+ return 0;
+ }
+ characterScore = 0.1;
+ if (string[indexInString] === character) {
+ characterScore += 0.1;
+ }
+ if (indexInString === 0 || string[indexInString - 1] === PathSeparator) {
+ characterScore += 0.8;
+ } else if ((_ref = string[indexInString - 1]) === '-' || _ref === '_' || _ref === ' ') {
+ characterScore += 0.7;
+ }
+ string = string.substring(indexInString + 1, stringLength);
+ totalCharacterScore += characterScore;
+ }
+ queryScore = totalCharacterScore / queryLength;
+ return ((queryScore * (queryLength / stringLength)) + queryScore) / 2;
+ };
+
+ queryIsLastPathSegment = function(string, query) {
+ if (string[string.length - query.length - 1] === PathSeparator) {
+ return string.lastIndexOf(query) === string.length - query.length;
+ }
+ };
+
+ exports.match = function(string, query, stringOffset) {
+ var character, indexInQuery, indexInString, lowerCaseIndex, matches, minIndex, queryLength, stringLength, upperCaseIndex, _i, _ref, _results;
+ if (stringOffset == null) {
+ stringOffset = 0;
+ }
+ if (string === query) {
+ return (function() {
+ _results = [];
+ for (var _i = stringOffset, _ref = stringOffset + string.length; stringOffset <= _ref ? _i < _ref : _i > _ref; stringOffset <= _ref ? _i++ : _i--){ _results.push(_i); }
+ return _results;
+ }).apply(this);
+ }
+ queryLength = query.length;
+ stringLength = string.length;
+ indexInQuery = 0;
+ indexInString = 0;
+ matches = [];
+ while (indexInQuery < queryLength) {
+ character = query[indexInQuery++];
+ lowerCaseIndex = string.indexOf(character.toLowerCase());
+ upperCaseIndex = string.indexOf(character.toUpperCase());
+ minIndex = Math.min(lowerCaseIndex, upperCaseIndex);
+ if (minIndex === -1) {
+ minIndex = Math.max(lowerCaseIndex, upperCaseIndex);
+ }
+ indexInString = minIndex;
+ if (indexInString === -1) {
+ return [];
+ }
+ matches.push(stringOffset + indexInString);
+ stringOffset += indexInString + 1;
+ string = string.substring(indexInString + 1, stringLength);
+ }
+ return matches;
+ };
+
+}).call(this);
+
+},{"path":7}],5:[function(require,module,exports){
+(function() {
+ var PathSeparator, scorer;
+
+ PathSeparator = require('path').sep;
+
+ scorer = require('./scorer');
+
+ exports.basenameMatch = function(subject, subject_lw, prepQuery) {
+ var basePos, depth, end;
+ end = subject.length - 1;
+ while (subject[end] === PathSeparator) {
+ end--;
+ }
+ basePos = subject.lastIndexOf(PathSeparator, end);
+ if (basePos === -1) {
+ return [];
+ }
+ depth = prepQuery.depth;
+ while (depth-- > 0) {
+ basePos = subject.lastIndexOf(PathSeparator, basePos - 1);
+ if (basePos === -1) {
+ return [];
+ }
+ }
+ basePos++;
+ end++;
+ return exports.match(subject.slice(basePos, end), subject_lw.slice(basePos, end), prepQuery, basePos);
+ };
+
+ exports.mergeMatches = function(a, b) {
+ var ai, bj, i, j, m, n, out;
+ m = a.length;
+ n = b.length;
+ if (n === 0) {
+ return a.slice();
+ }
+ if (m === 0) {
+ return b.slice();
+ }
+ i = -1;
+ j = 0;
+ bj = b[j];
+ out = [];
+ while (++i < m) {
+ ai = a[i];
+ while (bj <= ai && ++j < n) {
+ if (bj < ai) {
+ out.push(bj);
+ }
+ bj = b[j];
+ }
+ out.push(ai);
+ }
+ while (j < n) {
+ out.push(b[j++]);
+ }
+ return out;
+ };
+
+ exports.match = function(subject, subject_lw, prepQuery, offset) {
+ var DIAGONAL, LEFT, STOP, UP, acro_score, align, backtrack, csc_diag, csc_row, csc_score, i, j, m, matches, move, n, pos, query, query_lw, score, score_diag, score_row, score_up, si_lw, start, trace;
+ if (offset == null) {
+ offset = 0;
+ }
+ query = prepQuery.query;
+ query_lw = prepQuery.query_lw;
+ m = subject.length;
+ n = query.length;
+ acro_score = scorer.scoreAcronyms(subject, subject_lw, query, query_lw).score;
+ score_row = new Array(n);
+ csc_row = new Array(n);
+ STOP = 0;
+ UP = 1;
+ LEFT = 2;
+ DIAGONAL = 3;
+ trace = new Array(m * n);
+ pos = -1;
+ j = -1;
+ while (++j < n) {
+ score_row[j] = 0;
+ csc_row[j] = 0;
+ }
+ i = -1;
+ while (++i < m) {
+ score = 0;
+ score_up = 0;
+ csc_diag = 0;
+ si_lw = subject_lw[i];
+ j = -1;
+ while (++j < n) {
+ csc_score = 0;
+ align = 0;
+ score_diag = score_up;
+ if (query_lw[j] === si_lw) {
+ start = scorer.isWordStart(i, subject, subject_lw);
+ csc_score = csc_diag > 0 ? csc_diag : scorer.scoreConsecutives(subject, subject_lw, query, query_lw, i, j, start);
+ align = score_diag + scorer.scoreCharacter(i, j, start, acro_score, csc_score);
+ }
+ score_up = score_row[j];
+ csc_diag = csc_row[j];
+ if (score > score_up) {
+ move = LEFT;
+ } else {
+ score = score_up;
+ move = UP;
+ }
+ if (align > score) {
+ score = align;
+ move = DIAGONAL;
+ } else {
+ csc_score = 0;
+ }
+ score_row[j] = score;
+ csc_row[j] = csc_score;
+ trace[++pos] = score > 0 ? move : STOP;
+ }
+ }
+ i = m - 1;
+ j = n - 1;
+ pos = i * n + j;
+ backtrack = true;
+ matches = [];
+ while (backtrack && i >= 0 && j >= 0) {
+ switch (trace[pos]) {
+ case UP:
+ i--;
+ pos -= n;
+ break;
+ case LEFT:
+ j--;
+ pos--;
+ break;
+ case DIAGONAL:
+ matches.push(i + offset);
+ j--;
+ i--;
+ pos -= n + 1;
+ break;
+ default:
+ backtrack = false;
+ }
+ }
+ matches.reverse();
+ return matches;
+ };
+
+}).call(this);
+
+},{"./scorer":6,"path":7}],6:[function(require,module,exports){
+(function() {
+ var AcronymResult, PathSeparator, Query, basenameScore, coreChars, countDir, doScore, emptyAcronymResult, file_coeff, isMatch, isSeparator, isWordEnd, isWordStart, miss_coeff, opt_char_re, pos_bonus, scoreAcronyms, scoreCharacter, scoreConsecutives, scoreExact, scoreExactMatch, scorePattern, scorePosition, scoreSize, tau_depth, tau_size, truncatedUpperCase, wm;
+
+ PathSeparator = require('path').sep;
+
+ wm = 150;
+
+ pos_bonus = 20;
+
+ tau_depth = 13;
+
+ tau_size = 85;
+
+ file_coeff = 1.2;
+
+ miss_coeff = 0.75;
+
+ opt_char_re = /[ _\-:\/\\]/g;
+
+ exports.coreChars = coreChars = function(query) {
+ return query.replace(opt_char_re, '');
+ };
+
+ exports.score = function(string, query, prepQuery, allowErrors) {
+ var score, string_lw;
+ if (prepQuery == null) {
+ prepQuery = new Query(query);
+ }
+ if (allowErrors == null) {
+ allowErrors = false;
+ }
+ if (!(allowErrors || isMatch(string, prepQuery.core_lw, prepQuery.core_up))) {
+ return 0;
+ }
+ string_lw = string.toLowerCase();
+ score = doScore(string, string_lw, prepQuery);
+ return Math.ceil(basenameScore(string, string_lw, prepQuery, score));
+ };
+
+ Query = (function() {
+ function Query(query) {
+ if (!(query != null ? query.length : void 0)) {
+ return null;
+ }
+ this.query = query;
+ this.query_lw = query.toLowerCase();
+ this.core = coreChars(query);
+ this.core_lw = this.core.toLowerCase();
+ this.core_up = truncatedUpperCase(this.core);
+ this.depth = countDir(query, query.length);
+ }
+
+ return Query;
+
+ })();
+
+ exports.prepQuery = function(query) {
+ return new Query(query);
+ };
+
+ exports.isMatch = isMatch = function(subject, query_lw, query_up) {
+ var i, j, m, n, qj_lw, qj_up, si;
+ m = subject.length;
+ n = query_lw.length;
+ if (!m || n > m) {
+ return false;
+ }
+ i = -1;
+ j = -1;
+ while (++j < n) {
+ qj_lw = query_lw[j];
+ qj_up = query_up[j];
+ while (++i < m) {
+ si = subject[i];
+ if (si === qj_lw || si === qj_up) {
+ break;
+ }
+ }
+ if (i === m) {
+ return false;
+ }
+ }
+ return true;
+ };
+
+ doScore = function(subject, subject_lw, prepQuery) {
+ var acro, acro_score, align, csc_diag, csc_row, csc_score, i, j, m, miss_budget, miss_left, mm, n, pos, query, query_lw, record_miss, score, score_diag, score_row, score_up, si_lw, start, sz;
+ query = prepQuery.query;
+ query_lw = prepQuery.query_lw;
+ m = subject.length;
+ n = query.length;
+ acro = scoreAcronyms(subject, subject_lw, query, query_lw);
+ acro_score = acro.score;
+ if (acro.count === n) {
+ return scoreExact(n, m, acro_score, acro.pos);
+ }
+ pos = subject_lw.indexOf(query_lw);
+ if (pos > -1) {
+ return scoreExactMatch(subject, subject_lw, query, query_lw, pos, n, m);
+ }
+ score_row = new Array(n);
+ csc_row = new Array(n);
+ sz = scoreSize(n, m);
+ miss_budget = Math.ceil(miss_coeff * n) + 5;
+ miss_left = miss_budget;
+ j = -1;
+ while (++j < n) {
+ score_row[j] = 0;
+ csc_row[j] = 0;
+ }
+ i = subject_lw.indexOf(query_lw[0]);
+ if (i > -1) {
+ i--;
+ }
+ mm = subject_lw.lastIndexOf(query_lw[n - 1], m);
+ if (mm > i) {
+ m = mm + 1;
+ }
+ while (++i < m) {
+ score = 0;
+ score_diag = 0;
+ csc_diag = 0;
+ si_lw = subject_lw[i];
+ record_miss = true;
+ j = -1;
+ while (++j < n) {
+ score_up = score_row[j];
+ if (score_up > score) {
+ score = score_up;
+ }
+ csc_score = 0;
+ if (query_lw[j] === si_lw) {
+ start = isWordStart(i, subject, subject_lw);
+ csc_score = csc_diag > 0 ? csc_diag : scoreConsecutives(subject, subject_lw, query, query_lw, i, j, start);
+ align = score_diag + scoreCharacter(i, j, start, acro_score, csc_score);
+ if (align > score) {
+ score = align;
+ miss_left = miss_budget;
+ } else {
+ if (record_miss && --miss_left <= 0) {
+ return score_row[n - 1] * sz;
+ }
+ record_miss = false;
+ }
+ }
+ score_diag = score_up;
+ csc_diag = csc_row[j];
+ csc_row[j] = csc_score;
+ score_row[j] = score;
+ }
+ }
+ return score * sz;
+ };
+
+ exports.isWordStart = isWordStart = function(pos, subject, subject_lw) {
+ var curr_s, prev_s;
+ if (pos === 0) {
+ return true;
+ }
+ curr_s = subject[pos];
+ prev_s = subject[pos - 1];
+ return isSeparator(curr_s) || isSeparator(prev_s) || (curr_s !== subject_lw[pos] && prev_s === subject_lw[pos - 1]);
+ };
+
+ exports.isWordEnd = isWordEnd = function(pos, subject, subject_lw, len) {
+ var curr_s, next_s;
+ if (pos === len - 1) {
+ return true;
+ }
+ curr_s = subject[pos];
+ next_s = subject[pos + 1];
+ return isSeparator(curr_s) || isSeparator(next_s) || (curr_s === subject_lw[pos] && next_s !== subject_lw[pos + 1]);
+ };
+
+ isSeparator = function(c) {
+ return c === ' ' || c === '.' || c === '-' || c === '_' || c === '/' || c === '\\';
+ };
+
+ scorePosition = function(pos) {
+ var sc;
+ if (pos < pos_bonus) {
+ sc = pos_bonus - pos;
+ return 100 + sc * sc;
+ } else {
+ return Math.max(100 + pos_bonus - pos, 0);
+ }
+ };
+
+ scoreSize = function(n, m) {
+ return tau_size / (tau_size + Math.abs(m - n));
+ };
+
+ scoreExact = function(n, m, quality, pos) {
+ return 2 * n * (wm * quality + scorePosition(pos)) * scoreSize(n, m);
+ };
+
+ exports.scorePattern = scorePattern = function(count, len, sameCase, start, end) {
+ var bonus, sz;
+ sz = count;
+ bonus = 6;
+ if (sameCase === count) {
+ bonus += 2;
+ }
+ if (start) {
+ bonus += 3;
+ }
+ if (end) {
+ bonus += 1;
+ }
+ if (count === len) {
+ if (start) {
+ if (sameCase === len) {
+ sz += 2;
+ } else {
+ sz += 1;
+ }
+ }
+ if (end) {
+ bonus += 1;
+ }
+ }
+ return sameCase + sz * (sz + bonus);
+ };
+
+ exports.scoreCharacter = scoreCharacter = function(i, j, start, acro_score, csc_score) {
+ var posBonus;
+ posBonus = scorePosition(i);
+ if (start) {
+ return posBonus + wm * ((acro_score > csc_score ? acro_score : csc_score) + 10);
+ }
+ return posBonus + wm * csc_score;
+ };
+
+ exports.scoreConsecutives = scoreConsecutives = function(subject, subject_lw, query, query_lw, i, j, start) {
+ var k, m, mi, n, nj, sameCase, startPos, sz;
+ m = subject.length;
+ n = query.length;
+ mi = m - i;
+ nj = n - j;
+ k = mi < nj ? mi : nj;
+ startPos = i;
+ sameCase = 0;
+ sz = 0;
+ if (query[j] === subject[i]) {
+ sameCase++;
+ }
+ while (++sz < k && query_lw[++j] === subject_lw[++i]) {
+ if (query[j] === subject[i]) {
+ sameCase++;
+ }
+ }
+ if (sz === 1) {
+ return 1 + 2 * sameCase;
+ }
+ return scorePattern(sz, n, sameCase, start, isWordEnd(i, subject, subject_lw, m));
+ };
+
+ exports.scoreExactMatch = scoreExactMatch = function(subject, subject_lw, query, query_lw, pos, n, m) {
+ var end, i, pos2, sameCase, start;
+ start = isWordStart(pos, subject, subject_lw);
+ if (!start) {
+ pos2 = subject_lw.indexOf(query_lw, pos + 1);
+ if (pos2 > -1) {
+ start = isWordStart(pos2, subject, subject_lw);
+ if (start) {
+ pos = pos2;
+ }
+ }
+ }
+ i = -1;
+ sameCase = 0;
+ while (++i < n) {
+ if (query[pos + i] === subject[i]) {
+ sameCase++;
+ }
+ }
+ end = isWordEnd(pos + n - 1, subject, subject_lw, m);
+ return scoreExact(n, m, scorePattern(n, n, sameCase, start, end), pos);
+ };
+
+ AcronymResult = (function() {
+ function AcronymResult(score, pos, count) {
+ this.score = score;
+ this.pos = pos;
+ this.count = count;
+ }
+
+ return AcronymResult;
+
+ })();
+
+ emptyAcronymResult = new AcronymResult(0, 0.1, 0);
+
+ exports.scoreAcronyms = scoreAcronyms = function(subject, subject_lw, query, query_lw) {
+ var count, i, j, m, n, pos, qj_lw, sameCase, score;
+ m = subject.length;
+ n = query.length;
+ if (!(m > 1 && n > 1)) {
+ return emptyAcronymResult;
+ }
+ count = 0;
+ pos = 0;
+ sameCase = 0;
+ i = -1;
+ j = -1;
+ while (++j < n) {
+ qj_lw = query_lw[j];
+ while (++i < m) {
+ if (qj_lw === subject_lw[i] && isWordStart(i, subject, subject_lw)) {
+ if (query[j] === subject[i]) {
+ sameCase++;
+ }
+ pos += i;
+ count++;
+ break;
+ }
+ }
+ if (i === m) {
+ break;
+ }
+ }
+ if (count < 2) {
+ return emptyAcronymResult;
+ }
+ score = scorePattern(count, n, sameCase, true, false);
+ return new AcronymResult(score, pos / count, count);
+ };
+
+ basenameScore = function(subject, subject_lw, prepQuery, fullPathScore) {
+ var alpha, basePathScore, basePos, depth, end;
+ if (fullPathScore === 0) {
+ return 0;
+ }
+ end = subject.length - 1;
+ while (subject[end] === PathSeparator) {
+ end--;
+ }
+ basePos = subject.lastIndexOf(PathSeparator, end);
+ if (basePos === -1) {
+ return fullPathScore;
+ }
+ depth = prepQuery.depth;
+ while (depth-- > 0) {
+ basePos = subject.lastIndexOf(PathSeparator, basePos - 1);
+ if (basePos === -1) {
+ return fullPathScore;
+ }
+ }
+ basePos++;
+ end++;
+ basePathScore = doScore(subject.slice(basePos, end), subject_lw.slice(basePos, end), prepQuery);
+ alpha = 0.5 * tau_depth / (tau_depth + countDir(subject, end + 1));
+ return alpha * basePathScore + (1 - alpha) * fullPathScore * scoreSize(0, file_coeff * (end - basePos));
+ };
+
+ exports.countDir = countDir = function(path, end) {
+ var count, i;
+ if (end < 1) {
+ return 0;
+ }
+ count = 0;
+ i = -1;
+ while (++i < end && path[i] === PathSeparator) {
+ continue;
+ }
+ while (++i < end) {
+ if (path[i] === PathSeparator) {
+ count++;
+ while (++i < end && path[i] === PathSeparator) {
+ continue;
+ }
+ }
+ }
+ return count;
+ };
+
+ truncatedUpperCase = function(str) {
+ var char, upper, _i, _len;
+ upper = "";
+ for (_i = 0, _len = str.length; _i < _len; _i++) {
+ char = str[_i];
+ upper += char.toUpperCase()[0];
+ }
+ return upper;
+ };
+
+}).call(this);
+
+},{"path":7}],7:[function(require,module,exports){
+(function (process){
+// Copyright Joyent, Inc. and other Node contributors.
+//
+// Permission is hereby granted, free of charge, to any person obtaining a
+// copy of this software and associated documentation files (the
+// "Software"), to deal in the Software without restriction, including
+// without limitation the rights to use, copy, modify, merge, publish,
+// distribute, sublicense, and/or sell copies of the Software, and to permit
+// persons to whom the Software is furnished to do so, subject to the
+// following conditions:
+//
+// The above copyright notice and this permission notice shall be included
+// in all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
+// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
+// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
+// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
+// USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+// resolves . and .. elements in a path array with directory names there
+// must be no slashes, empty elements, or device names (c:\) in the array
+// (so also no leading and trailing slashes - it does not distinguish
+// relative and absolute paths)
+function normalizeArray(parts, allowAboveRoot) {
+ // if the path tries to go above the root, `up` ends up > 0
+ var up = 0;
+ for (var i = parts.length - 1; i >= 0; i--) {
+ var last = parts[i];
+ if (last === '.') {
+ parts.splice(i, 1);
+ } else if (last === '..') {
+ parts.splice(i, 1);
+ up++;
+ } else if (up) {
+ parts.splice(i, 1);
+ up--;
+ }
+ }
+
+ // if the path is allowed to go above the root, restore leading ..s
+ if (allowAboveRoot) {
+ for (; up--; up) {
+ parts.unshift('..');
+ }
+ }
+
+ return parts;
+}
+
+// Split a filename into [root, dir, basename, ext], unix version
+// 'root' is just a slash, or nothing.
+var splitPathRe =
+ /^(\/?|)([\s\S]*?)((?:\.{1,2}|[^\/]+?|)(\.[^.\/]*|))(?:[\/]*)$/;
+var splitPath = function(filename) {
+ return splitPathRe.exec(filename).slice(1);
+};
+
+// path.resolve([from ...], to)
+// posix version
+exports.resolve = function() {
+ var resolvedPath = '',
+ resolvedAbsolute = false;
+
+ for (var i = arguments.length - 1; i >= -1 && !resolvedAbsolute; i--) {
+ var path = (i >= 0) ? arguments[i] : process.cwd();
+
+ // Skip empty and invalid entries
+ if (typeof path !== 'string') {
+ throw new TypeError('Arguments to path.resolve must be strings');
+ } else if (!path) {
+ continue;
+ }
+
+ resolvedPath = path + '/' + resolvedPath;
+ resolvedAbsolute = path.charAt(0) === '/';
+ }
+
+ // At this point the path should be resolved to a full absolute path, but
+ // handle relative paths to be safe (might happen when process.cwd() fails)
+
+ // Normalize the path
+ resolvedPath = normalizeArray(filter(resolvedPath.split('/'), function(p) {
+ return !!p;
+ }), !resolvedAbsolute).join('/');
+
+ return ((resolvedAbsolute ? '/' : '') + resolvedPath) || '.';
+};
+
+// path.normalize(path)
+// posix version
+exports.normalize = function(path) {
+ var isAbsolute = exports.isAbsolute(path),
+ trailingSlash = substr(path, -1) === '/';
+
+ // Normalize the path
+ path = normalizeArray(filter(path.split('/'), function(p) {
+ return !!p;
+ }), !isAbsolute).join('/');
+
+ if (!path && !isAbsolute) {
+ path = '.';
+ }
+ if (path && trailingSlash) {
+ path += '/';
+ }
+
+ return (isAbsolute ? '/' : '') + path;
+};
+
+// posix version
+exports.isAbsolute = function(path) {
+ return path.charAt(0) === '/';
+};
+
+// posix version
+exports.join = function() {
+ var paths = Array.prototype.slice.call(arguments, 0);
+ return exports.normalize(filter(paths, function(p, index) {
+ if (typeof p !== 'string') {
+ throw new TypeError('Arguments to path.join must be strings');
+ }
+ return p;
+ }).join('/'));
+};
+
+
+// path.relative(from, to)
+// posix version
+exports.relative = function(from, to) {
+ from = exports.resolve(from).substr(1);
+ to = exports.resolve(to).substr(1);
+
+ function trim(arr) {
+ var start = 0;
+ for (; start < arr.length; start++) {
+ if (arr[start] !== '') break;
+ }
+
+ var end = arr.length - 1;
+ for (; end >= 0; end--) {
+ if (arr[end] !== '') break;
+ }
+
+ if (start > end) return [];
+ return arr.slice(start, end - start + 1);
+ }
+
+ var fromParts = trim(from.split('/'));
+ var toParts = trim(to.split('/'));
+
+ var length = Math.min(fromParts.length, toParts.length);
+ var samePartsLength = length;
+ for (var i = 0; i < length; i++) {
+ if (fromParts[i] !== toParts[i]) {
+ samePartsLength = i;
+ break;
+ }
+ }
+
+ var outputParts = [];
+ for (var i = samePartsLength; i < fromParts.length; i++) {
+ outputParts.push('..');
+ }
+
+ outputParts = outputParts.concat(toParts.slice(samePartsLength));
+
+ return outputParts.join('/');
+};
+
+exports.sep = '/';
+exports.delimiter = ':';
+
+exports.dirname = function(path) {
+ var result = splitPath(path),
+ root = result[0],
+ dir = result[1];
+
+ if (!root && !dir) {
+ // No dirname whatsoever
+ return '.';
+ }
+
+ if (dir) {
+ // It has a dirname, strip trailing slash
+ dir = dir.substr(0, dir.length - 1);
+ }
+
+ return root + dir;
+};
+
+
+exports.basename = function(path, ext) {
+ var f = splitPath(path)[2];
+ // TODO: make this comparison case-insensitive on windows?
+ if (ext && f.substr(-1 * ext.length) === ext) {
+ f = f.substr(0, f.length - ext.length);
+ }
+ return f;
+};
+
+
+exports.extname = function(path) {
+ return splitPath(path)[3];
+};
+
+function filter (xs, f) {
+ if (xs.filter) return xs.filter(f);
+ var res = [];
+ for (var i = 0; i < xs.length; i++) {
+ if (f(xs[i], i, xs)) res.push(xs[i]);
+ }
+ return res;
+}
+
+// String.prototype.substr - negative index don't work in IE8
+var substr = 'ab'.substr(-1) === 'b'
+ ? function (str, start, len) { return str.substr(start, len) }
+ : function (str, start, len) {
+ if (start < 0) start = str.length + start;
+ return str.substr(start, len);
+ }
+;
+
+}).call(this,require('_process'))
+},{"_process":8}],8:[function(require,module,exports){
+// shim for using process in browser
+
+var process = module.exports = {};
+var queue = [];
+var draining = false;
+var currentQueue;
+var queueIndex = -1;
+
+function cleanUpNextTick() {
+ draining = false;
+ if (currentQueue.length) {
+ queue = currentQueue.concat(queue);
+ } else {
+ queueIndex = -1;
+ }
+ if (queue.length) {
+ drainQueue();
+ }
+}
+
+function drainQueue() {
+ if (draining) {
+ return;
+ }
+ var timeout = setTimeout(cleanUpNextTick);
+ draining = true;
+
+ var len = queue.length;
+ while(len) {
+ currentQueue = queue;
+ queue = [];
+ while (++queueIndex < len) {
+ if (currentQueue) {
+ currentQueue[queueIndex].run();
+ }
+ }
+ queueIndex = -1;
+ len = queue.length;
+ }
+ currentQueue = null;
+ draining = false;
+ clearTimeout(timeout);
+}
+
+process.nextTick = function (fun) {
+ var args = new Array(arguments.length - 1);
+ if (arguments.length > 1) {
+ for (var i = 1; i < arguments.length; i++) {
+ args[i - 1] = arguments[i];
+ }
+ }
+ queue.push(new Item(fun, args));
+ if (queue.length === 1 && !draining) {
+ setTimeout(drainQueue, 0);
+ }
+};
+
+// v8 likes predictible objects
+function Item(fun, array) {
+ this.fun = fun;
+ this.array = array;
+}
+Item.prototype.run = function () {
+ this.fun.apply(null, this.array);
+};
+process.title = 'browser';
+process.browser = true;
+process.env = {};
+process.argv = [];
+process.version = ''; // empty string to avoid regexp issues
+process.versions = {};
+
+function noop() {}
+
+process.on = noop;
+process.addListener = noop;
+process.once = noop;
+process.off = noop;
+process.removeListener = noop;
+process.removeAllListeners = noop;
+process.emit = noop;
+
+process.binding = function (name) {
+ throw new Error('process.binding is not supported');
+};
+
+process.cwd = function () { return '/' };
+process.chdir = function (dir) {
+ throw new Error('process.chdir is not supported');
+};
+process.umask = function() { return 0; };
+
+},{}]},{},[1]);
diff --git a/vendor/assets/javascripts/g.bar.js b/vendor/assets/javascripts/g.bar.js
new file mode 100644
index 0000000000..166bd654d6
--- /dev/null
+++ b/vendor/assets/javascripts/g.bar.js
@@ -0,0 +1,674 @@
+/*!
+ * g.Raphael 0.51 - Charting library, based on Raphaël
+ *
+ * Copyright (c) 2009-2012 Dmitry Baranovskiy (http://g.raphaeljs.com)
+ * Licensed under the MIT (http://www.opensource.org/licenses/mit-license.php) license.
+ */
+(function () {
+ var mmin = Math.min,
+ mmax = Math.max;
+
+ function finger(x, y, width, height, dir, ending, isPath, paper) {
+ var path,
+ ends = { round: 'round', sharp: 'sharp', soft: 'soft', square: 'square' };
+
+ // dir 0 for horizontal and 1 for vertical
+ if ((dir && !height) || (!dir && !width)) {
+ return isPath ? "" : paper.path();
+ }
+
+ ending = ends[ending] || "square";
+ height = Math.round(height);
+ width = Math.round(width);
+ x = Math.round(x);
+ y = Math.round(y);
+
+ switch (ending) {
+ case "round":
+ if (!dir) {
+ var r = ~~(height / 2);
+
+ if (width < r) {
+ r = width;
+ path = [
+ "M", x + .5, y + .5 - ~~(height / 2),
+ "l", 0, 0,
+ "a", r, ~~(height / 2), 0, 0, 1, 0, height,
+ "l", 0, 0,
+ "z"
+ ];
+ } else {
+ path = [
+ "M", x + .5, y + .5 - r,
+ "l", width - r, 0,
+ "a", r, r, 0, 1, 1, 0, height,
+ "l", r - width, 0,
+ "z"
+ ];
+ }
+ } else {
+ r = ~~(width / 2);
+
+ if (height < r) {
+ r = height;
+ path = [
+ "M", x - ~~(width / 2), y,
+ "l", 0, 0,
+ "a", ~~(width / 2), r, 0, 0, 1, width, 0,
+ "l", 0, 0,
+ "z"
+ ];
+ } else {
+ path = [
+ "M", x - r, y,
+ "l", 0, r - height,
+ "a", r, r, 0, 1, 1, width, 0,
+ "l", 0, height - r,
+ "z"
+ ];
+ }
+ }
+ break;
+ case "sharp":
+ if (!dir) {
+ var half = ~~(height / 2);
+
+ path = [
+ "M", x, y + half,
+ "l", 0, -height, mmax(width - half, 0), 0, mmin(half, width), half, -mmin(half, width), half + (half * 2 < height),
+ "z"
+ ];
+ } else {
+ half = ~~(width / 2);
+ path = [
+ "M", x + half, y,
+ "l", -width, 0, 0, -mmax(height - half, 0), half, -mmin(half, height), half, mmin(half, height), half,
+ "z"
+ ];
+ }
+ break;
+ case "square":
+ if (!dir) {
+ path = [
+ "M", x, y + ~~(height / 2),
+ "l", 0, -height, width, 0, 0, height,
+ "z"
+ ];
+ } else {
+ path = [
+ "M", x + ~~(width / 2), y,
+ "l", 1 - width, 0, 0, -height, width - 1, 0,
+ "z"
+ ];
+ }
+ break;
+ case "soft":
+ if (!dir) {
+ r = mmin(width, Math.round(height / 5));
+ path = [
+ "M", x + .5, y + .5 - ~~(height / 2),
+ "l", width - r, 0,
+ "a", r, r, 0, 0, 1, r, r,
+ "l", 0, height - r * 2,
+ "a", r, r, 0, 0, 1, -r, r,
+ "l", r - width, 0,
+ "z"
+ ];
+ } else {
+ r = mmin(Math.round(width / 5), height);
+ path = [
+ "M", x - ~~(width / 2), y,
+ "l", 0, r - height,
+ "a", r, r, 0, 0, 1, r, -r,
+ "l", width - 2 * r, 0,
+ "a", r, r, 0, 0, 1, r, r,
+ "l", 0, height - r,
+ "z"
+ ];
+ }
+ }
+
+ if (isPath) {
+ return path.join(",");
+ } else {
+ return paper.path(path);
+ }
+ }
+
+/*\
+ * Paper.vbarchart
+ [ method ]
+ **
+ * Creates a vertical bar chart
+ **
+ > Parameters
+ **
+ - x (number) x coordinate of the chart
+ - y (number) y coordinate of the chart
+ - width (number) width of the chart (respected by all elements in the set)
+ - height (number) height of the chart (respected by all elements in the set)
+ - values (array) values
+ - opts (object) options for the chart
+ o {
+ o type (string) type of endings of the bar. Default: 'square'. Other options are: 'round', 'sharp', 'soft'.
+ o gutter (number)(string) default '20%' (WHAT DOES IT DO?)
+ o vgutter (number)
+ o colors (array) colors be used repeatedly to plot the bars. If multicolumn bar is used each sequence of bars with use a different color.
+ o stacked (boolean) whether or not to tread values as in a stacked bar chart
+ o to
+ o stretch (boolean)
+ o }
+ **
+ = (object) path element of the popup
+ > Usage
+ | r.vbarchart(0, 0, 620, 260, [76, 70, 67, 71, 69], {})
+ \*/
+
+ function VBarchart(paper, x, y, width, height, values, opts) {
+ opts = opts || {};
+
+ var chartinst = this,
+ type = opts.type || "square",
+ gutter = parseFloat(opts.gutter || "20%"),
+ chart = paper.set(),
+ bars = paper.set(),
+ covers = paper.set(),
+ covers2 = paper.set(),
+ total = Math.max.apply(Math, values),
+ stacktotal = [],
+ multi = 0,
+ colors = opts.colors || chartinst.colors,
+ len = values.length;
+
+ if (Raphael.is(values[0], "array")) {
+ total = [];
+ multi = len;
+ len = 0;
+
+ for (var i = values.length; i--;) {
+ bars.push(paper.set());
+ total.push(Math.max.apply(Math, values[i]));
+ len = Math.max(len, values[i].length);
+ }
+
+ if (opts.stacked) {
+ for (var i = len; i--;) {
+ var tot = 0;
+
+ for (var j = values.length; j--;) {
+ tot +=+ values[j][i] || 0;
+ }
+
+ stacktotal.push(tot);
+ }
+ }
+
+ for (var i = values.length; i--;) {
+ if (values[i].length < len) {
+ for (var j = len; j--;) {
+ values[i].push(0);
+ }
+ }
+ }
+
+ total = Math.max.apply(Math, opts.stacked ? stacktotal : total);
+ }
+
+ total = (opts.to) || total;
+
+ var barwidth = width / (len * (100 + gutter) + gutter) * 100,
+ barhgutter = barwidth * gutter / 100,
+ barvgutter = opts.vgutter == null ? 20 : opts.vgutter,
+ stack = [],
+ X = x + barhgutter,
+ Y = (height - 2 * barvgutter) / total;
+
+ if (!opts.stretch) {
+ barhgutter = Math.round(barhgutter);
+ barwidth = Math.floor(barwidth);
+ }
+
+ !opts.stacked && (barwidth /= multi || 1);
+
+ for (var i = 0; i < len; i++) {
+ stack = [];
+
+ for (var j = 0; j < (multi || 1); j++) {
+ var h = Math.round((multi ? values[j][i] : values[i]) * Y),
+ top = y + height - barvgutter - h,
+ bar = finger(Math.round(X + barwidth / 2), top + h, barwidth, h, true, type, null, paper).attr({ stroke: "none", fill: colors[multi ? j : i] });
+
+ if (multi) {
+ bars[j].push(bar);
+ } else {
+ bars.push(bar);
+ }
+
+ bar.y = top;
+ bar.x = Math.round(X + barwidth / 2);
+ bar.w = barwidth;
+ bar.h = h;
+ bar.value = multi ? values[j][i] : values[i];
+
+ if (!opts.stacked) {
+ X += barwidth;
+ } else {
+ stack.push(bar);
+ }
+ }
+
+ if (opts.stacked) {
+ var cvr;
+
+ covers2.push(cvr = paper.rect(stack[0].x - stack[0].w / 2, y, barwidth, height).attr(chartinst.shim));
+ cvr.bars = paper.set();
+
+ var size = 0;
+
+ for (var s = stack.length; s--;) {
+ stack[s].toFront();
+ }
+
+ for (var s = 0, ss = stack.length; s < ss; s++) {
+ var bar = stack[s],
+ cover,
+ h = (size + bar.value) * Y,
+ path = finger(bar.x, y + height - barvgutter - !!size * .5, barwidth, h, true, type, 1, paper);
+
+ cvr.bars.push(bar);
+ size && bar.attr({path: path});
+ bar.h = h;
+ bar.y = y + height - barvgutter - !!size * .5 - h;
+ covers.push(cover = paper.rect(bar.x - bar.w / 2, bar.y, barwidth, bar.value * Y).attr(chartinst.shim));
+ cover.bar = bar;
+ cover.value = bar.value;
+ size += bar.value;
+ }
+
+ X += barwidth;
+ }
+
+ X += barhgutter;
+ }
+
+ covers2.toFront();
+ X = x + barhgutter;
+
+ if (!opts.stacked) {
+ for (var i = 0; i < len; i++) {
+ for (var j = 0; j < (multi || 1); j++) {
+ var cover;
+
+ covers.push(cover = paper.rect(Math.round(X), y + barvgutter, barwidth, height - barvgutter).attr(chartinst.shim));
+ cover.bar = multi ? bars[j][i] : bars[i];
+ cover.value = cover.bar.value;
+ X += barwidth;
+ }
+
+ X += barhgutter;
+ }
+ }
+
+ chart.label = function (labels, isBottom) {
+ labels = labels || [];
+ this.labels = paper.set();
+
+ var L, l = -Infinity;
+
+ if (opts.stacked) {
+ for (var i = 0; i < len; i++) {
+ var tot = 0;
+
+ for (var j = 0; j < (multi || 1); j++) {
+ tot += multi ? values[j][i] : values[i];
+
+ if (j == multi - 1) {
+ var label = paper.labelise(labels[i], tot, total);
+
+ L = paper.text(bars[i * (multi || 1) + j].x, y + height - barvgutter / 2, label).attr(txtattr).insertBefore(covers[i * (multi || 1) + j]);
+
+ var bb = L.getBBox();
+
+ if (bb.x - 7 < l) {
+ L.remove();
+ } else {
+ this.labels.push(L);
+ l = bb.x + bb.width;
+ }
+ }
+ }
+ }
+ } else {
+ for (var i = 0; i < len; i++) {
+ for (var j = 0; j < (multi || 1); j++) {
+ var label = paper.labelise(multi ? labels[j] && labels[j][i] : labels[i], multi ? values[j][i] : values[i], total);
+
+ L = paper.text(bars[i * (multi || 1) + j].x, isBottom ? y + height - barvgutter / 2 : bars[i * (multi || 1) + j].y - 10, label).attr(txtattr).insertBefore(covers[i * (multi || 1) + j]);
+
+ var bb = L.getBBox();
+
+ if (bb.x - 7 < l) {
+ L.remove();
+ } else {
+ this.labels.push(L);
+ l = bb.x + bb.width;
+ }
+ }
+ }
+ }
+ return this;
+ };
+
+ chart.hover = function (fin, fout) {
+ covers2.hide();
+ covers.show();
+ covers.mouseover(fin).mouseout(fout);
+ return this;
+ };
+
+ chart.hoverColumn = function (fin, fout) {
+ covers.hide();
+ covers2.show();
+ fout = fout || function () {};
+ covers2.mouseover(fin).mouseout(fout);
+ return this;
+ };
+
+ chart.click = function (f) {
+ covers2.hide();
+ covers.show();
+ covers.click(f);
+ return this;
+ };
+
+ chart.each = function (f) {
+ if (!Raphael.is(f, "function")) {
+ return this;
+ }
+ for (var i = covers.length; i--;) {
+ f.call(covers[i]);
+ }
+ return this;
+ };
+
+ chart.eachColumn = function (f) {
+ if (!Raphael.is(f, "function")) {
+ return this;
+ }
+ for (var i = covers2.length; i--;) {
+ f.call(covers2[i]);
+ }
+ return this;
+ };
+
+ chart.clickColumn = function (f) {
+ covers.hide();
+ covers2.show();
+ covers2.click(f);
+ return this;
+ };
+
+ chart.push(bars, covers, covers2);
+ chart.bars = bars;
+ chart.covers = covers;
+ return chart;
+ };
+
+ //inheritance
+ var F = function() {};
+ F.prototype = Raphael.g;
+ HBarchart.prototype = VBarchart.prototype = new F; //prototype reused by hbarchart
+
+ Raphael.fn.barchart = function(x, y, width, height, values, opts) {
+ return new VBarchart(this, x, y, width, height, values, opts);
+ };
+
+/*\
+ * Paper.barchart
+ [ method ]
+ **
+ * Creates a horizontal bar chart
+ **
+ > Parameters
+ **
+ - x (number) x coordinate of the chart
+ - y (number) y coordinate of the chart
+ - width (number) width of the chart (respected by all elements in the set)
+ - height (number) height of the chart (respected by all elements in the set)
+ - values (array) values
+ - opts (object) options for the chart
+ o {
+ o type (string) type of endings of the bar. Default: 'square'. Other options are: 'round', 'sharp', 'soft'.
+ o gutter (number)(string) default '20%' (WHAT DOES IT DO?)
+ o vgutter (number)
+ o colors (array) colors be used repeatedly to plot the bars. If multicolumn bar is used each sequence of bars with use a different color.
+ o stacked (boolean) whether or not to tread values as in a stacked bar chart
+ o to
+ o stretch (boolean)
+ o }
+ **
+ = (object) path element of the popup
+ > Usage
+ | r.barchart(0, 0, 620, 260, [76, 70, 67, 71, 69], {})
+ \*/
+
+ function HBarchart(paper, x, y, width, height, values, opts) {
+ opts = opts || {};
+
+ var chartinst = this,
+ type = opts.type || "square",
+ gutter = parseFloat(opts.gutter || "20%"),
+ chart = paper.set(),
+ bars = paper.set(),
+ covers = paper.set(),
+ covers2 = paper.set(),
+ total = Math.max.apply(Math, values),
+ stacktotal = [],
+ multi = 0,
+ colors = opts.colors || chartinst.colors,
+ len = values.length;
+
+ if (Raphael.is(values[0], "array")) {
+ total = [];
+ multi = len;
+ len = 0;
+
+ for (var i = values.length; i--;) {
+ bars.push(paper.set());
+ total.push(Math.max.apply(Math, values[i]));
+ len = Math.max(len, values[i].length);
+ }
+
+ if (opts.stacked) {
+ for (var i = len; i--;) {
+ var tot = 0;
+ for (var j = values.length; j--;) {
+ tot +=+ values[j][i] || 0;
+ }
+ stacktotal.push(tot);
+ }
+ }
+
+ for (var i = values.length; i--;) {
+ if (values[i].length < len) {
+ for (var j = len; j--;) {
+ values[i].push(0);
+ }
+ }
+ }
+
+ total = Math.max.apply(Math, opts.stacked ? stacktotal : total);
+ }
+
+ total = (opts.to) || total;
+
+ var barheight = Math.floor(height / (len * (100 + gutter) + gutter) * 100),
+ bargutter = Math.floor(barheight * gutter / 100),
+ stack = [],
+ Y = y + bargutter,
+ X = (width - 1) / total;
+
+ !opts.stacked && (barheight /= multi || 1);
+
+ for (var i = 0; i < len; i++) {
+ stack = [];
+
+ for (var j = 0; j < (multi || 1); j++) {
+ var val = multi ? values[j][i] : values[i],
+ bar = finger(x, Y + barheight / 2, Math.round(val * X), barheight - 1, false, type, null, paper).attr({stroke: "none", fill: colors[multi ? j : i]});
+
+ if (multi) {
+ bars[j].push(bar);
+ } else {
+ bars.push(bar);
+ }
+
+ bar.x = x + Math.round(val * X);
+ bar.y = Y + barheight / 2;
+ bar.w = Math.round(val * X);
+ bar.h = barheight;
+ bar.value = +val;
+
+ if (!opts.stacked) {
+ Y += barheight;
+ } else {
+ stack.push(bar);
+ }
+ }
+
+ if (opts.stacked) {
+ var cvr = paper.rect(x, stack[0].y - stack[0].h / 2, width, barheight).attr(chartinst.shim);
+
+ covers2.push(cvr);
+ cvr.bars = paper.set();
+
+ var size = 0;
+
+ for (var s = stack.length; s--;) {
+ stack[s].toFront();
+ }
+
+ for (var s = 0, ss = stack.length; s < ss; s++) {
+ var bar = stack[s],
+ cover,
+ val = Math.round((size + bar.value) * X),
+ path = finger(x, bar.y, val, barheight - 1, false, type, 1, paper);
+
+ cvr.bars.push(bar);
+ size && bar.attr({ path: path });
+ bar.w = val;
+ bar.x = x + val;
+ covers.push(cover = paper.rect(x + size * X, bar.y - bar.h / 2, bar.value * X, barheight).attr(chartinst.shim));
+ cover.bar = bar;
+ size += bar.value;
+ }
+
+ Y += barheight;
+ }
+
+ Y += bargutter;
+ }
+
+ covers2.toFront();
+ Y = y + bargutter;
+
+ if (!opts.stacked) {
+ for (var i = 0; i < len; i++) {
+ for (var j = 0; j < (multi || 1); j++) {
+ var cover = paper.rect(x, Y, width, barheight).attr(chartinst.shim);
+
+ covers.push(cover);
+ cover.bar = multi ? bars[j][i] : bars[i];
+ cover.value = cover.bar.value;
+ Y += barheight;
+ }
+
+ Y += bargutter;
+ }
+ }
+
+ chart.label = function (labels, isRight) {
+ labels = labels || [];
+ this.labels = paper.set();
+
+ for (var i = 0; i < len; i++) {
+ for (var j = 0; j < multi; j++) {
+ var label = paper.labelise(multi ? labels[j] && labels[j][i] : labels[i], multi ? values[j][i] : values[i], total),
+ X = isRight ? bars[i * (multi || 1) + j].x - barheight / 2 + 3 : x + 5,
+ A = isRight ? "end" : "start",
+ L;
+
+ this.labels.push(L = paper.text(X, bars[i * (multi || 1) + j].y, label).attr(txtattr).attr({ "text-anchor": A }).insertBefore(covers[0]));
+
+ if (L.getBBox().x < x + 5) {
+ L.attr({x: x + 5, "text-anchor": "start"});
+ } else {
+ bars[i * (multi || 1) + j].label = L;
+ }
+ }
+ }
+
+ return this;
+ };
+
+ chart.hover = function (fin, fout) {
+ covers2.hide();
+ covers.show();
+ fout = fout || function () {};
+ covers.mouseover(fin).mouseout(fout);
+ return this;
+ };
+
+ chart.hoverColumn = function (fin, fout) {
+ covers.hide();
+ covers2.show();
+ fout = fout || function () {};
+ covers2.mouseover(fin).mouseout(fout);
+ return this;
+ };
+
+ chart.each = function (f) {
+ if (!Raphael.is(f, "function")) {
+ return this;
+ }
+ for (var i = covers.length; i--;) {
+ f.call(covers[i]);
+ }
+ return this;
+ };
+
+ chart.eachColumn = function (f) {
+ if (!Raphael.is(f, "function")) {
+ return this;
+ }
+ for (var i = covers2.length; i--;) {
+ f.call(covers2[i]);
+ }
+ return this;
+ };
+
+ chart.click = function (f) {
+ covers2.hide();
+ covers.show();
+ covers.click(f);
+ return this;
+ };
+
+ chart.clickColumn = function (f) {
+ covers.hide();
+ covers2.show();
+ covers2.click(f);
+ return this;
+ };
+
+ chart.push(bars, covers, covers2);
+ chart.bars = bars;
+ chart.covers = covers;
+ return chart;
+ };
+
+ Raphael.fn.hbarchart = function(x, y, width, height, values, opts) {
+ return new HBarchart(this, x, y, width, height, values, opts);
+ };
+
+})();
diff --git a/vendor/assets/javascripts/g.raphael.js b/vendor/assets/javascripts/g.raphael.js
new file mode 100644
index 0000000000..27f27caf9f
--- /dev/null
+++ b/vendor/assets/javascripts/g.raphael.js
@@ -0,0 +1,861 @@
+/*!
+ * g.Raphael 0.51 - Charting library, based on Raphaël
+ *
+ * Copyright (c) 2009-2012 Dmitry Baranovskiy (http://g.raphaeljs.com)
+ * Licensed under the MIT (http://www.opensource.org/licenses/mit-license.php) license.
+ */
+
+/*
+ * Tooltips on Element prototype
+ */
+/*\
+ * Element.popup
+ [ method ]
+ **
+ * Puts the context Element in a 'popup' tooltip. Can also be used on sets.
+ **
+ > Parameters
+ **
+ - dir (string) location of Element relative to the tail: `'down'`, `'left'`, `'up'` [default], or `'right'`.
+ - size (number) amount of bevel/padding around the Element, as well as half the width and height of the tail [default: `5`]
+ - x (number) x coordinate of the popup's tail [default: Element's `x` or `cx`]
+ - y (number) y coordinate of the popup's tail [default: Element's `y` or `cy`]
+ **
+ = (object) path element of the popup
+ \*/
+Raphael.el.popup = function (dir, size, x, y) {
+ var paper = this.paper || this[0].paper,
+ bb, xy, center, cw, ch;
+
+ if (!paper) return;
+
+ switch (this.type) {
+ case 'text':
+ case 'circle':
+ case 'ellipse': center = true; break;
+ default: center = false;
+ }
+
+ dir = dir == null ? 'up' : dir;
+ size = size || 5;
+ bb = this.getBBox();
+
+ x = typeof x == 'number' ? x : (center ? bb.x + bb.width / 2 : bb.x);
+ y = typeof y == 'number' ? y : (center ? bb.y + bb.height / 2 : bb.y);
+ cw = Math.max(bb.width / 2 - size, 0);
+ ch = Math.max(bb.height / 2 - size, 0);
+
+ this.translate(x - bb.x - (center ? bb.width / 2 : 0), y - bb.y - (center ? bb.height / 2 : 0));
+ bb = this.getBBox();
+
+ var paths = {
+ up: [
+ 'M', x, y,
+ 'l', -size, -size, -cw, 0,
+ 'a', size, size, 0, 0, 1, -size, -size,
+ 'l', 0, -bb.height,
+ 'a', size, size, 0, 0, 1, size, -size,
+ 'l', size * 2 + cw * 2, 0,
+ 'a', size, size, 0, 0, 1, size, size,
+ 'l', 0, bb.height,
+ 'a', size, size, 0, 0, 1, -size, size,
+ 'l', -cw, 0,
+ 'z'
+ ].join(','),
+ down: [
+ 'M', x, y,
+ 'l', size, size, cw, 0,
+ 'a', size, size, 0, 0, 1, size, size,
+ 'l', 0, bb.height,
+ 'a', size, size, 0, 0, 1, -size, size,
+ 'l', -(size * 2 + cw * 2), 0,
+ 'a', size, size, 0, 0, 1, -size, -size,
+ 'l', 0, -bb.height,
+ 'a', size, size, 0, 0, 1, size, -size,
+ 'l', cw, 0,
+ 'z'
+ ].join(','),
+ left: [
+ 'M', x, y,
+ 'l', -size, size, 0, ch,
+ 'a', size, size, 0, 0, 1, -size, size,
+ 'l', -bb.width, 0,
+ 'a', size, size, 0, 0, 1, -size, -size,
+ 'l', 0, -(size * 2 + ch * 2),
+ 'a', size, size, 0, 0, 1, size, -size,
+ 'l', bb.width, 0,
+ 'a', size, size, 0, 0, 1, size, size,
+ 'l', 0, ch,
+ 'z'
+ ].join(','),
+ right: [
+ 'M', x, y,
+ 'l', size, -size, 0, -ch,
+ 'a', size, size, 0, 0, 1, size, -size,
+ 'l', bb.width, 0,
+ 'a', size, size, 0, 0, 1, size, size,
+ 'l', 0, size * 2 + ch * 2,
+ 'a', size, size, 0, 0, 1, -size, size,
+ 'l', -bb.width, 0,
+ 'a', size, size, 0, 0, 1, -size, -size,
+ 'l', 0, -ch,
+ 'z'
+ ].join(',')
+ };
+
+ xy = {
+ up: { x: -!center * (bb.width / 2), y: -size * 2 - (center ? bb.height / 2 : bb.height) },
+ down: { x: -!center * (bb.width / 2), y: size * 2 + (center ? bb.height / 2 : bb.height) },
+ left: { x: -size * 2 - (center ? bb.width / 2 : bb.width), y: -!center * (bb.height / 2) },
+ right: { x: size * 2 + (center ? bb.width / 2 : bb.width), y: -!center * (bb.height / 2) }
+ }[dir];
+
+ this.translate(xy.x, xy.y);
+ return paper.path(paths[dir]).attr({ fill: "#000", stroke: "none" }).insertBefore(this.node ? this : this[0]);
+};
+
+/*\
+ * Element.tag
+ [ method ]
+ **
+ * Puts the context Element in a 'tag' tooltip. Can also be used on sets.
+ **
+ > Parameters
+ **
+ - angle (number) angle of orientation in degrees [default: `0`]
+ - r (number) radius of the loop [default: `5`]
+ - x (number) x coordinate of the center of the tag loop [default: Element's `x` or `cx`]
+ - y (number) y coordinate of the center of the tag loop [default: Element's `x` or `cx`]
+ **
+ = (object) path element of the tag
+ \*/
+Raphael.el.tag = function (angle, r, x, y) {
+ var d = 3,
+ paper = this.paper || this[0].paper;
+
+ if (!paper) return;
+
+ var p = paper.path().attr({ fill: '#000', stroke: '#000' }),
+ bb = this.getBBox(),
+ dx, R, center, tmp;
+
+ switch (this.type) {
+ case 'text':
+ case 'circle':
+ case 'ellipse': center = true; break;
+ default: center = false;
+ }
+
+ angle = angle || 0;
+ x = typeof x == 'number' ? x : (center ? bb.x + bb.width / 2 : bb.x);
+ y = typeof y == 'number' ? y : (center ? bb.y + bb.height / 2 : bb.y);
+ r = r == null ? 5 : r;
+ R = .5522 * r;
+
+ if (bb.height >= r * 2) {
+ p.attr({
+ path: [
+ "M", x, y + r,
+ "a", r, r, 0, 1, 1, 0, -r * 2, r, r, 0, 1, 1, 0, r * 2,
+ "m", 0, -r * 2 -d,
+ "a", r + d, r + d, 0, 1, 0, 0, (r + d) * 2,
+ "L", x + r + d, y + bb.height / 2 + d,
+ "l", bb.width + 2 * d, 0, 0, -bb.height - 2 * d, -bb.width - 2 * d, 0,
+ "L", x, y - r - d
+ ].join(",")
+ });
+ } else {
+ dx = Math.sqrt(Math.pow(r + d, 2) - Math.pow(bb.height / 2 + d, 2));
+ p.attr({
+ path: [
+ "M", x, y + r,
+ "c", -R, 0, -r, R - r, -r, -r, 0, -R, r - R, -r, r, -r, R, 0, r, r - R, r, r, 0, R, R - r, r, -r, r,
+ "M", x + dx, y - bb.height / 2 - d,
+ "a", r + d, r + d, 0, 1, 0, 0, bb.height + 2 * d,
+ "l", r + d - dx + bb.width + 2 * d, 0, 0, -bb.height - 2 * d,
+ "L", x + dx, y - bb.height / 2 - d
+ ].join(",")
+ });
+ }
+
+ angle = 360 - angle;
+ p.rotate(angle, x, y);
+
+ if (this.attrs) {
+ //elements
+ this.attr(this.attrs.x ? 'x' : 'cx', x + r + d + (!center ? this.type == 'text' ? bb.width : 0 : bb.width / 2)).attr('y', center ? y : y - bb.height / 2);
+ this.rotate(angle, x, y);
+ angle > 90 && angle < 270 && this.attr(this.attrs.x ? 'x' : 'cx', x - r - d - (!center ? bb.width : bb.width / 2)).rotate(180, x, y);
+ } else {
+ //sets
+ if (angle > 90 && angle < 270) {
+ this.translate(x - bb.x - bb.width - r - d, y - bb.y - bb.height / 2);
+ this.rotate(angle - 180, bb.x + bb.width + r + d, bb.y + bb.height / 2);
+ } else {
+ this.translate(x - bb.x + r + d, y - bb.y - bb.height / 2);
+ this.rotate(angle, bb.x - r - d, bb.y + bb.height / 2);
+ }
+ }
+
+ return p.insertBefore(this.node ? this : this[0]);
+};
+
+/*\
+ * Element.drop
+ [ method ]
+ **
+ * Puts the context Element in a 'drop' tooltip. Can also be used on sets.
+ **
+ > Parameters
+ **
+ - angle (number) angle of orientation in degrees [default: `0`]
+ - x (number) x coordinate of the drop's point [default: Element's `x` or `cx`]
+ - y (number) y coordinate of the drop's point [default: Element's `x` or `cx`]
+ **
+ = (object) path element of the drop
+ \*/
+Raphael.el.drop = function (angle, x, y) {
+ var bb = this.getBBox(),
+ paper = this.paper || this[0].paper,
+ center, size, p, dx, dy;
+
+ if (!paper) return;
+
+ switch (this.type) {
+ case 'text':
+ case 'circle':
+ case 'ellipse': center = true; break;
+ default: center = false;
+ }
+
+ angle = angle || 0;
+
+ x = typeof x == 'number' ? x : (center ? bb.x + bb.width / 2 : bb.x);
+ y = typeof y == 'number' ? y : (center ? bb.y + bb.height / 2 : bb.y);
+ size = Math.max(bb.width, bb.height) + Math.min(bb.width, bb.height);
+ p = paper.path([
+ "M", x, y,
+ "l", size, 0,
+ "A", size * .4, size * .4, 0, 1, 0, x + size * .7, y - size * .7,
+ "z"
+ ]).attr({fill: "#000", stroke: "none"}).rotate(22.5 - angle, x, y);
+
+ angle = (angle + 90) * Math.PI / 180;
+ dx = (x + size * Math.sin(angle)) - (center ? 0 : bb.width / 2);
+ dy = (y + size * Math.cos(angle)) - (center ? 0 : bb.height / 2);
+
+ this.attrs ?
+ this.attr(this.attrs.x ? 'x' : 'cx', dx).attr(this.attrs.y ? 'y' : 'cy', dy) :
+ this.translate(dx - bb.x, dy - bb.y);
+
+ return p.insertBefore(this.node ? this : this[0]);
+};
+
+/*\
+ * Element.flag
+ [ method ]
+ **
+ * Puts the context Element in a 'flag' tooltip. Can also be used on sets.
+ **
+ > Parameters
+ **
+ - angle (number) angle of orientation in degrees [default: `0`]
+ - x (number) x coordinate of the flag's point [default: Element's `x` or `cx`]
+ - y (number) y coordinate of the flag's point [default: Element's `x` or `cx`]
+ **
+ = (object) path element of the flag
+ \*/
+Raphael.el.flag = function (angle, x, y) {
+ var d = 3,
+ paper = this.paper || this[0].paper;
+
+ if (!paper) return;
+
+ var p = paper.path().attr({ fill: '#000', stroke: '#000' }),
+ bb = this.getBBox(),
+ h = bb.height / 2,
+ center;
+
+ switch (this.type) {
+ case 'text':
+ case 'circle':
+ case 'ellipse': center = true; break;
+ default: center = false;
+ }
+
+ angle = angle || 0;
+ x = typeof x == 'number' ? x : (center ? bb.x + bb.width / 2 : bb.x);
+ y = typeof y == 'number' ? y : (center ? bb.y + bb.height / 2: bb.y);
+
+ p.attr({
+ path: [
+ "M", x, y,
+ "l", h + d, -h - d, bb.width + 2 * d, 0, 0, bb.height + 2 * d, -bb.width - 2 * d, 0,
+ "z"
+ ].join(",")
+ });
+
+ angle = 360 - angle;
+ p.rotate(angle, x, y);
+
+ if (this.attrs) {
+ //elements
+ this.attr(this.attrs.x ? 'x' : 'cx', x + h + d + (!center ? this.type == 'text' ? bb.width : 0 : bb.width / 2)).attr('y', center ? y : y - bb.height / 2);
+ this.rotate(angle, x, y);
+ angle > 90 && angle < 270 && this.attr(this.attrs.x ? 'x' : 'cx', x - h - d - (!center ? bb.width : bb.width / 2)).rotate(180, x, y);
+ } else {
+ //sets
+ if (angle > 90 && angle < 270) {
+ this.translate(x - bb.x - bb.width - h - d, y - bb.y - bb.height / 2);
+ this.rotate(angle - 180, bb.x + bb.width + h + d, bb.y + bb.height / 2);
+ } else {
+ this.translate(x - bb.x + h + d, y - bb.y - bb.height / 2);
+ this.rotate(angle, bb.x - h - d, bb.y + bb.height / 2);
+ }
+ }
+
+ return p.insertBefore(this.node ? this : this[0]);
+};
+
+/*\
+ * Element.label
+ [ method ]
+ **
+ * Puts the context Element in a 'label' tooltip. Can also be used on sets.
+ **
+ = (object) path element of the label.
+ \*/
+Raphael.el.label = function () {
+ var bb = this.getBBox(),
+ paper = this.paper || this[0].paper,
+ r = Math.min(20, bb.width + 10, bb.height + 10) / 2;
+
+ if (!paper) return;
+
+ return paper.rect(bb.x - r / 2, bb.y - r / 2, bb.width + r, bb.height + r, r).attr({ stroke: 'none', fill: '#000' }).insertBefore(this.node ? this : this[0]);
+};
+
+/*\
+ * Element.blob
+ [ method ]
+ **
+ * Puts the context Element in a 'blob' tooltip. Can also be used on sets.
+ **
+ > Parameters
+ **
+ - angle (number) angle of orientation in degrees [default: `0`]
+ - x (number) x coordinate of the blob's tail [default: Element's `x` or `cx`]
+ - y (number) y coordinate of the blob's tail [default: Element's `x` or `cx`]
+ **
+ = (object) path element of the blob
+ \*/
+Raphael.el.blob = function (angle, x, y) {
+ var bb = this.getBBox(),
+ rad = Math.PI / 180,
+ paper = this.paper || this[0].paper,
+ p, center, size;
+
+ if (!paper) return;
+
+ switch (this.type) {
+ case 'text':
+ case 'circle':
+ case 'ellipse': center = true; break;
+ default: center = false;
+ }
+
+ p = paper.path().attr({ fill: "#000", stroke: "none" });
+ angle = (+angle + 1 ? angle : 45) + 90;
+ size = Math.min(bb.height, bb.width);
+ x = typeof x == 'number' ? x : (center ? bb.x + bb.width / 2 : bb.x);
+ y = typeof y == 'number' ? y : (center ? bb.y + bb.height / 2 : bb.y);
+
+ var w = Math.max(bb.width + size, size * 25 / 12),
+ h = Math.max(bb.height + size, size * 25 / 12),
+ x2 = x + size * Math.sin((angle - 22.5) * rad),
+ y2 = y + size * Math.cos((angle - 22.5) * rad),
+ x1 = x + size * Math.sin((angle + 22.5) * rad),
+ y1 = y + size * Math.cos((angle + 22.5) * rad),
+ dx = (x1 - x2) / 2,
+ dy = (y1 - y2) / 2,
+ rx = w / 2,
+ ry = h / 2,
+ k = -Math.sqrt(Math.abs(rx * rx * ry * ry - rx * rx * dy * dy - ry * ry * dx * dx) / (rx * rx * dy * dy + ry * ry * dx * dx)),
+ cx = k * rx * dy / ry + (x1 + x2) / 2,
+ cy = k * -ry * dx / rx + (y1 + y2) / 2;
+
+ p.attr({
+ x: cx,
+ y: cy,
+ path: [
+ "M", x, y,
+ "L", x1, y1,
+ "A", rx, ry, 0, 1, 1, x2, y2,
+ "z"
+ ].join(",")
+ });
+
+ this.translate(cx - bb.x - bb.width / 2, cy - bb.y - bb.height / 2);
+
+ return p.insertBefore(this.node ? this : this[0]);
+};
+
+/*
+ * Tooltips on Paper prototype
+ */
+/*\
+ * Paper.label
+ [ method ]
+ **
+ * Puts the given `text` into a 'label' tooltip. The text is given a default style according to @g.txtattr. See @Element.label
+ **
+ > Parameters
+ **
+ - x (number) x coordinate of the center of the label
+ - y (number) y coordinate of the center of the label
+ - text (string) text to place inside the label
+ **
+ = (object) set containing the label path and the text element
+ > Usage
+ | paper.label(50, 50, "$9.99");
+ \*/
+Raphael.fn.label = function (x, y, text) {
+ var set = this.set();
+
+ text = this.text(x, y, text).attr(Raphael.g.txtattr);
+ return set.push(text.label(), text);
+};
+
+/*\
+ * Paper.popup
+ [ method ]
+ **
+ * Puts the given `text` into a 'popup' tooltip. The text is given a default style according to @g.txtattr. See @Element.popup
+ *
+ * Note: The `dir` parameter has changed from g.Raphael 0.4.1 to 0.5. The options `0`, `1`, `2`, and `3` has been changed to `'down'`, `'left'`, `'up'`, and `'right'` respectively.
+ **
+ > Parameters
+ **
+ - x (number) x coordinate of the popup's tail
+ - y (number) y coordinate of the popup's tail
+ - text (string) text to place inside the popup
+ - dir (string) location of the text relative to the tail: `'down'`, `'left'`, `'up'` [default], or `'right'`.
+ - size (number) amount of padding around the Element [default: `5`]
+ **
+ = (object) set containing the popup path and the text element
+ > Usage
+ | paper.popup(50, 50, "$9.99", 'down');
+ \*/
+Raphael.fn.popup = function (x, y, text, dir, size) {
+ var set = this.set();
+
+ text = this.text(x, y, text).attr(Raphael.g.txtattr);
+ return set.push(text.popup(dir, size), text);
+};
+
+/*\
+ * Paper.tag
+ [ method ]
+ **
+ * Puts the given text into a 'tag' tooltip. The text is given a default style according to @g.txtattr. See @Element.tag
+ **
+ > Parameters
+ **
+ - x (number) x coordinate of the center of the tag loop
+ - y (number) y coordinate of the center of the tag loop
+ - text (string) text to place inside the tag
+ - angle (number) angle of orientation in degrees [default: `0`]
+ - r (number) radius of the loop [default: `5`]
+ **
+ = (object) set containing the tag path and the text element
+ > Usage
+ | paper.tag(50, 50, "$9.99", 60);
+ \*/
+Raphael.fn.tag = function (x, y, text, angle, r) {
+ var set = this.set();
+
+ text = this.text(x, y, text).attr(Raphael.g.txtattr);
+ return set.push(text.tag(angle, r), text);
+};
+
+/*\
+ * Paper.flag
+ [ method ]
+ **
+ * Puts the given `text` into a 'flag' tooltip. The text is given a default style according to @g.txtattr. See @Element.flag
+ **
+ > Parameters
+ **
+ - x (number) x coordinate of the flag's point
+ - y (number) y coordinate of the flag's point
+ - text (string) text to place inside the flag
+ - angle (number) angle of orientation in degrees [default: `0`]
+ **
+ = (object) set containing the flag path and the text element
+ > Usage
+ | paper.flag(50, 50, "$9.99", 60);
+ \*/
+Raphael.fn.flag = function (x, y, text, angle) {
+ var set = this.set();
+
+ text = this.text(x, y, text).attr(Raphael.g.txtattr);
+ return set.push(text.flag(angle), text);
+};
+
+/*\
+ * Paper.drop
+ [ method ]
+ **
+ * Puts the given text into a 'drop' tooltip. The text is given a default style according to @g.txtattr. See @Element.drop
+ **
+ > Parameters
+ **
+ - x (number) x coordinate of the drop's point
+ - y (number) y coordinate of the drop's point
+ - text (string) text to place inside the drop
+ - angle (number) angle of orientation in degrees [default: `0`]
+ **
+ = (object) set containing the drop path and the text element
+ > Usage
+ | paper.drop(50, 50, "$9.99", 60);
+ \*/
+Raphael.fn.drop = function (x, y, text, angle) {
+ var set = this.set();
+
+ text = this.text(x, y, text).attr(Raphael.g.txtattr);
+ return set.push(text.drop(angle), text);
+};
+
+/*\
+ * Paper.blob
+ [ method ]
+ **
+ * Puts the given text into a 'blob' tooltip. The text is given a default style according to @g.txtattr. See @Element.blob
+ **
+ > Parameters
+ **
+ - x (number) x coordinate of the blob's tail
+ - y (number) y coordinate of the blob's tail
+ - text (string) text to place inside the blob
+ - angle (number) angle of orientation in degrees [default: `0`]
+ **
+ = (object) set containing the blob path and the text element
+ > Usage
+ | paper.blob(50, 50, "$9.99", 60);
+ \*/
+Raphael.fn.blob = function (x, y, text, angle) {
+ var set = this.set();
+
+ text = this.text(x, y, text).attr(Raphael.g.txtattr);
+ return set.push(text.blob(angle), text);
+};
+
+/**
+ * Brightness functions on the Element prototype
+ */
+/*\
+ * Element.lighter
+ [ method ]
+ **
+ * Makes the context element lighter by increasing the brightness and reducing the saturation by a given factor. Can be called on Sets.
+ **
+ > Parameters
+ **
+ - times (number) adjustment factor [default: `2`]
+ **
+ = (object) Element
+ > Usage
+ | paper.circle(50, 50, 20).attr({
+ | fill: "#ff0000",
+ | stroke: "#fff",
+ | "stroke-width": 2
+ | }).lighter(6);
+ \*/
+Raphael.el.lighter = function (times) {
+ times = times || 2;
+
+ var fs = [this.attrs.fill, this.attrs.stroke];
+
+ this.fs = this.fs || [fs[0], fs[1]];
+
+ fs[0] = Raphael.rgb2hsb(Raphael.getRGB(fs[0]).hex);
+ fs[1] = Raphael.rgb2hsb(Raphael.getRGB(fs[1]).hex);
+ fs[0].b = Math.min(fs[0].b * times, 1);
+ fs[0].s = fs[0].s / times;
+ fs[1].b = Math.min(fs[1].b * times, 1);
+ fs[1].s = fs[1].s / times;
+
+ this.attr({fill: "hsb(" + [fs[0].h, fs[0].s, fs[0].b] + ")", stroke: "hsb(" + [fs[1].h, fs[1].s, fs[1].b] + ")"});
+ return this;
+};
+
+/*\
+ * Element.darker
+ [ method ]
+ **
+ * Makes the context element darker by decreasing the brightness and increasing the saturation by a given factor. Can be called on Sets.
+ **
+ > Parameters
+ **
+ - times (number) adjustment factor [default: `2`]
+ **
+ = (object) Element
+ > Usage
+ | paper.circle(50, 50, 20).attr({
+ | fill: "#ff0000",
+ | stroke: "#fff",
+ | "stroke-width": 2
+ | }).darker(6);
+ \*/
+Raphael.el.darker = function (times) {
+ times = times || 2;
+
+ var fs = [this.attrs.fill, this.attrs.stroke];
+
+ this.fs = this.fs || [fs[0], fs[1]];
+
+ fs[0] = Raphael.rgb2hsb(Raphael.getRGB(fs[0]).hex);
+ fs[1] = Raphael.rgb2hsb(Raphael.getRGB(fs[1]).hex);
+ fs[0].s = Math.min(fs[0].s * times, 1);
+ fs[0].b = fs[0].b / times;
+ fs[1].s = Math.min(fs[1].s * times, 1);
+ fs[1].b = fs[1].b / times;
+
+ this.attr({fill: "hsb(" + [fs[0].h, fs[0].s, fs[0].b] + ")", stroke: "hsb(" + [fs[1].h, fs[1].s, fs[1].b] + ")"});
+ return this;
+};
+
+/*\
+ * Element.resetBrightness
+ [ method ]
+ **
+ * Resets brightness and saturation levels to their original values. See @Element.lighter and @Element.darker. Can be called on Sets.
+ **
+ = (object) Element
+ > Usage
+ | paper.circle(50, 50, 20).attr({
+ | fill: "#ff0000",
+ | stroke: "#fff",
+ | "stroke-width": 2
+ | }).lighter(6).resetBrightness();
+ \*/
+Raphael.el.resetBrightness = function () {
+ if (this.fs) {
+ this.attr({ fill: this.fs[0], stroke: this.fs[1] });
+ delete this.fs;
+ }
+ return this;
+};
+
+//alias to set prototype
+(function () {
+ var brightness = ['lighter', 'darker', 'resetBrightness'],
+ tooltips = ['popup', 'tag', 'flag', 'label', 'drop', 'blob'];
+
+ for (var f in tooltips) (function (name) {
+ Raphael.st[name] = function () {
+ return Raphael.el[name].apply(this, arguments);
+ };
+ })(tooltips[f]);
+
+ for (var f in brightness) (function (name) {
+ Raphael.st[name] = function () {
+ for (var i = 0; i < this.length; i++) {
+ this[i][name].apply(this[i], arguments);
+ }
+
+ return this;
+ };
+ })(brightness[f]);
+})();
+
+//chart prototype for storing common functions
+Raphael.g = {
+ /*\
+ * g.shim
+ [ object ]
+ **
+ * An attribute object that charts will set on all generated shims (shims being the invisible objects that mouse events are bound to)
+ **
+ > Default value
+ | { stroke: 'none', fill: '#000', 'fill-opacity': 0 }
+ \*/
+ shim: { stroke: 'none', fill: '#000', 'fill-opacity': 0 },
+
+ /*\
+ * g.txtattr
+ [ object ]
+ **
+ * An attribute object that charts and tooltips will set on any generated text
+ **
+ > Default value
+ | { font: '12px Arial, sans-serif', fill: '#fff' }
+ \*/
+ txtattr: { font: '12px Arial, sans-serif', fill: '#fff' },
+
+ /*\
+ * g.colors
+ [ array ]
+ **
+ * An array of color values that charts will iterate through when drawing chart data values.
+ **
+ \*/
+ colors: (function () {
+ var hues = [.6, .2, .05, .1333, .75, 0],
+ colors = [];
+
+ for (var i = 0; i < 10; i++) {
+ if (i < hues.length) {
+ colors.push('hsb(' + hues[i] + ',.75, .75)');
+ } else {
+ colors.push('hsb(' + hues[i - hues.length] + ', 1, .5)');
+ }
+ }
+
+ return colors;
+ })(),
+
+ snapEnds: function(from, to, steps) {
+ var f = from,
+ t = to;
+
+ if (f == t) {
+ return {from: f, to: t, power: 0};
+ }
+
+ function round(a) {
+ return Math.abs(a - .5) < .25 ? ~~(a) + .5 : Math.round(a);
+ }
+
+ var d = (t - f) / steps,
+ r = ~~(d),
+ R = r,
+ i = 0;
+
+ if (r) {
+ while (R) {
+ i--;
+ R = ~~(d * Math.pow(10, i)) / Math.pow(10, i);
+ }
+
+ i ++;
+ } else {
+ if(d == 0 || !isFinite(d)) {
+ i = 1;
+ } else {
+ while (!r) {
+ i = i || 1;
+ r = ~~(d * Math.pow(10, i)) / Math.pow(10, i);
+ i++;
+ }
+ }
+
+ i && i--;
+ }
+
+ t = round(to * Math.pow(10, i)) / Math.pow(10, i);
+
+ if (t < to) {
+ t = round((to + .5) * Math.pow(10, i)) / Math.pow(10, i);
+ }
+
+ f = round((from - (i > 0 ? 0 : .5)) * Math.pow(10, i)) / Math.pow(10, i);
+ return { from: f, to: t, power: i };
+ },
+
+ axis: function (x, y, length, from, to, steps, orientation, labels, type, dashsize, paper) {
+ dashsize = dashsize == null ? 2 : dashsize;
+ type = type || "t";
+ steps = steps || 10;
+ paper = arguments[arguments.length-1] //paper is always last argument
+
+ var path = type == "|" || type == " " ? ["M", x + .5, y, "l", 0, .001] : orientation == 1 || orientation == 3 ? ["M", x + .5, y, "l", 0, -length] : ["M", x, y + .5, "l", length, 0],
+ ends = this.snapEnds(from, to, steps),
+ f = ends.from,
+ t = ends.to,
+ i = ends.power,
+ j = 0,
+ txtattr = { font: "11px 'Fontin Sans', Fontin-Sans, sans-serif" },
+ text = paper.set(),
+ d;
+
+ d = (t - f) / steps;
+
+ var label = f,
+ rnd = i > 0 ? i : 0;
+ dx = length / steps;
+
+ if (+orientation == 1 || +orientation == 3) {
+ var Y = y,
+ addon = (orientation - 1 ? 1 : -1) * (dashsize + 3 + !!(orientation - 1));
+
+ while (Y >= y - length) {
+ type != "-" && type != " " && (path = path.concat(["M", x - (type == "+" || type == "|" ? dashsize : !(orientation - 1) * dashsize * 2), Y + .5, "l", dashsize * 2 + 1, 0]));
+ text.push(paper.text(x + addon, Y, (labels && labels[j++]) || (Math.round(label) == label ? label : +label.toFixed(rnd))).attr(txtattr).attr({ "text-anchor": orientation - 1 ? "start" : "end" }));
+ label += d;
+ Y -= dx;
+ }
+
+ if (Math.round(Y + dx - (y - length))) {
+ type != "-" && type != " " && (path = path.concat(["M", x - (type == "+" || type == "|" ? dashsize : !(orientation - 1) * dashsize * 2), y - length + .5, "l", dashsize * 2 + 1, 0]));
+ text.push(paper.text(x + addon, y - length, (labels && labels[j]) || (Math.round(label) == label ? label : +label.toFixed(rnd))).attr(txtattr).attr({ "text-anchor": orientation - 1 ? "start" : "end" }));
+ }
+ } else {
+ label = f;
+ rnd = (i > 0) * i;
+ addon = (orientation ? -1 : 1) * (dashsize + 9 + !orientation);
+
+ var X = x,
+ dx = length / steps,
+ txt = 0,
+ prev = 0;
+
+ while (X <= x + length) {
+ type != "-" && type != " " && (path = path.concat(["M", X + .5, y - (type == "+" ? dashsize : !!orientation * dashsize * 2), "l", 0, dashsize * 2 + 1]));
+ text.push(txt = paper.text(X, y + addon, (labels && labels[j++]) || (Math.round(label) == label ? label : +label.toFixed(rnd))).attr(txtattr));
+
+ var bb = txt.getBBox();
+
+ if (prev >= bb.x - 5) {
+ text.pop(text.length - 1).remove();
+ } else {
+ prev = bb.x + bb.width;
+ }
+
+ label += d;
+ X += dx;
+ }
+
+ if (Math.round(X - dx - x - length)) {
+ type != "-" && type != " " && (path = path.concat(["M", x + length + .5, y - (type == "+" ? dashsize : !!orientation * dashsize * 2), "l", 0, dashsize * 2 + 1]));
+ text.push(paper.text(x + length, y + addon, (labels && labels[j]) || (Math.round(label) == label ? label : +label.toFixed(rnd))).attr(txtattr));
+ }
+ }
+
+ var res = paper.path(path);
+
+ res.text = text;
+ res.all = paper.set([res, text]);
+ res.remove = function () {
+ this.text.remove();
+ this.constructor.prototype.remove.call(this);
+ };
+
+ return res;
+ },
+
+ labelise: function(label, val, total) {
+ if (label) {
+ return (label + "").replace(/(##+(?:\.#+)?)|(%%+(?:\.%+)?)/g, function (all, value, percent) {
+ if (value) {
+ return (+val).toFixed(value.replace(/^#+\.?/g, "").length);
+ }
+ if (percent) {
+ return (val * 100 / total).toFixed(percent.replace(/^%+\.?/g, "").length) + "%";
+ }
+ });
+ } else {
+ return (+val).toFixed(0);
+ }
+ }
+}
diff --git a/vendor/assets/javascripts/jquery.nicescroll.js b/vendor/assets/javascripts/jquery.nicescroll.js
new file mode 100644
index 0000000000..7653f25df4
--- /dev/null
+++ b/vendor/assets/javascripts/jquery.nicescroll.js
@@ -0,0 +1,3634 @@
+/* jquery.nicescroll
+-- version 3.6.0
+-- copyright 2014-11-21 InuYaksa*2014
+-- licensed under the MIT
+--
+-- http://nicescroll.areaaperta.com/
+-- https://github.com/inuyaksa/jquery.nicescroll
+--
+*/
+
+(function(factory) {
+ if (typeof define === 'function' && define.amd) {
+ // AMD. Register as anonymous module.
+ define(['jquery'], factory);
+ } else {
+ // Browser globals.
+ factory(jQuery);
+ }
+}(function(jQuery) {
+ "use strict";
+
+ // globals
+ var domfocus = false;
+ var mousefocus = false;
+ var tabindexcounter = 0;
+ var ascrailcounter = 2000;
+ var globalmaxzindex = 0;
+
+ var $ = jQuery; // sandbox
+
+ // http://stackoverflow.com/questions/2161159/get-script-path
+ function getScriptPath() {
+ var scripts = document.getElementsByTagName('script');
+ var path = scripts[scripts.length - 1].src.split('?')[0];
+ return (path.split('/').length > 0) ? path.split('/').slice(0, -1).join('/') + '/' : '';
+ }
+
+ var vendors = ['webkit','ms','moz','o'];
+
+ var setAnimationFrame = window.requestAnimationFrame || false;
+ var clearAnimationFrame = window.cancelAnimationFrame || false;
+
+ if (!setAnimationFrame) { // legacy detection
+ for (var vx in vendors) {
+ var v = vendors[vx];
+ if (!setAnimationFrame) setAnimationFrame = window[v + 'RequestAnimationFrame'];
+ if (!clearAnimationFrame) clearAnimationFrame = window[v + 'CancelAnimationFrame'] || window[v + 'CancelRequestAnimationFrame'];
+ }
+ }
+
+ var ClsMutationObserver = window.MutationObserver || window.WebKitMutationObserver || false;
+
+ var _globaloptions = {
+ zindex: "auto",
+ cursoropacitymin: 0,
+ cursoropacitymax: 1,
+ cursorcolor: "#424242",
+ cursorwidth: "5px",
+ cursorborder: "1px solid #fff",
+ cursorborderradius: "5px",
+ scrollspeed: 60,
+ mousescrollstep: 8 * 3,
+ touchbehavior: false,
+ hwacceleration: true,
+ usetransition: true,
+ boxzoom: false,
+ dblclickzoom: true,
+ gesturezoom: true,
+ grabcursorenabled: true,
+ autohidemode: true,
+ background: "",
+ iframeautoresize: true,
+ cursorminheight: 32,
+ preservenativescrolling: true,
+ railoffset: false,
+ railhoffset: false,
+ bouncescroll: true,
+ spacebarenabled: true,
+ railpadding: {
+ top: 0,
+ right: 0,
+ left: 0,
+ bottom: 0
+ },
+ disableoutline: true,
+ horizrailenabled: true,
+ railalign: "right",
+ railvalign: "bottom",
+ enabletranslate3d: true,
+ enablemousewheel: true,
+ enablekeyboard: true,
+ smoothscroll: true,
+ sensitiverail: true,
+ enablemouselockapi: true,
+ // cursormaxheight:false,
+ cursorfixedheight: false,
+ directionlockdeadzone: 6,
+ hidecursordelay: 400,
+ nativeparentscrolling: true,
+ enablescrollonselection: true,
+ overflowx: true,
+ overflowy: true,
+ cursordragspeed: 0.3,
+ rtlmode: "auto",
+ cursordragontouch: false,
+ oneaxismousemode: "auto",
+ scriptpath: getScriptPath(),
+ preventmultitouchscrolling: true
+ };
+
+ var browserdetected = false;
+
+ var getBrowserDetection = function() {
+
+ if (browserdetected) return browserdetected;
+
+ var _el = document.createElement('DIV'),
+ _style = _el.style,
+ _agent = navigator.userAgent,
+ _platform = navigator.platform,
+ d = {};
+
+ d.haspointerlock = "pointerLockElement" in document || "webkitPointerLockElement" in document || "mozPointerLockElement" in document;
+
+ d.isopera = ("opera" in window); // 12-
+ d.isopera12 = (d.isopera && ("getUserMedia" in navigator));
+ d.isoperamini = (Object.prototype.toString.call(window.operamini) === "[object OperaMini]");
+
+ d.isie = (("all" in document) && ("attachEvent" in _el) && !d.isopera); //IE10-
+ d.isieold = (d.isie && !("msInterpolationMode" in _style)); // IE6 and older
+ d.isie7 = d.isie && !d.isieold && (!("documentMode" in document) || (document.documentMode == 7));
+ d.isie8 = d.isie && ("documentMode" in document) && (document.documentMode == 8);
+ d.isie9 = d.isie && ("performance" in window) && (document.documentMode >= 9);
+ d.isie10 = d.isie && ("performance" in window) && (document.documentMode == 10);
+ d.isie11 = ("msRequestFullscreen" in _el) && (document.documentMode >= 11); // IE11+
+
+ d.isie9mobile = /iemobile.9/i.test(_agent); //wp 7.1 mango
+ if (d.isie9mobile) d.isie9 = false;
+ d.isie7mobile = (!d.isie9mobile && d.isie7) && /iemobile/i.test(_agent); //wp 7.0
+
+ d.ismozilla = ("MozAppearance" in _style);
+
+ d.iswebkit = ("WebkitAppearance" in _style);
+
+ d.ischrome = ("chrome" in window);
+ d.ischrome22 = (d.ischrome && d.haspointerlock);
+ d.ischrome26 = (d.ischrome && ("transition" in _style)); // issue with transform detection (maintain prefix)
+
+ d.cantouch = ("ontouchstart" in document.documentElement) || ("ontouchstart" in window); // detection for Chrome Touch Emulation
+ d.hasmstouch = (window.MSPointerEvent || false); // IE10 pointer events
+ d.hasw3ctouch = (window.PointerEvent || false); //IE11 pointer events, following W3C Pointer Events spec
+
+ d.ismac = /^mac$/i.test(_platform);
+
+ d.isios = (d.cantouch && /iphone|ipad|ipod/i.test(_platform));
+ d.isios4 = ((d.isios) && !("seal" in Object));
+ d.isios7 = ((d.isios)&&("webkitHidden" in document)); //iOS 7+
+
+ d.isandroid = (/android/i.test(_agent));
+
+ d.haseventlistener = ("addEventListener" in _el);
+
+ d.trstyle = false;
+ d.hastransform = false;
+ d.hastranslate3d = false;
+ d.transitionstyle = false;
+ d.hastransition = false;
+ d.transitionend = false;
+
+ var a;
+ var check = ['transform', 'msTransform', 'webkitTransform', 'MozTransform', 'OTransform'];
+ for (a = 0; a < check.length; a++) {
+ if (typeof _style[check[a]] != "undefined") {
+ d.trstyle = check[a];
+ break;
+ }
+ }
+ d.hastransform = (!!d.trstyle);
+ if (d.hastransform) {
+ _style[d.trstyle] = "translate3d(1px,2px,3px)";
+ d.hastranslate3d = /translate3d/.test(_style[d.trstyle]);
+ }
+
+ d.transitionstyle = false;
+ d.prefixstyle = '';
+ d.transitionend = false;
+ check = ['transition', 'webkitTransition', 'msTransition', 'MozTransition', 'OTransition', 'OTransition', 'KhtmlTransition'];
+ var prefix = ['', '-webkit-', '-ms-', '-moz-', '-o-', '-o', '-khtml-'];
+ var evs = ['transitionend', 'webkitTransitionEnd', 'msTransitionEnd', 'transitionend', 'otransitionend', 'oTransitionEnd', 'KhtmlTransitionEnd'];
+ for (a = 0; a < check.length; a++) {
+ if (check[a] in _style) {
+ d.transitionstyle = check[a];
+ d.prefixstyle = prefix[a];
+ d.transitionend = evs[a];
+ break;
+ }
+ }
+ if (d.ischrome26) { // always use prefix
+ d.prefixstyle = prefix[1];
+ }
+
+ d.hastransition = (d.transitionstyle);
+
+ function detectCursorGrab() {
+ var lst = ['-webkit-grab', '-moz-grab', 'grab'];
+ if ((d.ischrome && !d.ischrome22) || d.isie) lst = []; // force setting for IE returns false positive and chrome cursor bug
+ for (var a = 0; a < lst.length; a++) {
+ var p = lst[a];
+ _style.cursor = p;
+ if (_style.cursor == p) return p;
+ }
+ return 'url(//mail.google.com/mail/images/2/openhand.cur),n-resize'; // thank you google for custom cursor!
+ }
+ d.cursorgrabvalue = detectCursorGrab();
+
+ d.hasmousecapture = ("setCapture" in _el);
+
+ d.hasMutationObserver = (ClsMutationObserver !== false);
+
+ _el = null; //memory released
+
+ browserdetected = d;
+
+ return d;
+ };
+
+ var NiceScrollClass = function(myopt, me) {
+
+ var self = this;
+
+ this.version = '3.6.0';
+ this.name = 'nicescroll';
+
+ this.me = me;
+
+ this.opt = {
+ doc: $("body"),
+ win: false
+ };
+
+ $.extend(this.opt, _globaloptions); // clone opts
+
+ // Options for internal use
+ this.opt.snapbackspeed = 80;
+
+ if (myopt || false) {
+ for (var a in self.opt) {
+ if (typeof myopt[a] != "undefined") self.opt[a] = myopt[a];
+ }
+ }
+
+ this.doc = self.opt.doc;
+ this.iddoc = (this.doc && this.doc[0]) ? this.doc[0].id || '' : '';
+ this.ispage = /^BODY|HTML/.test((self.opt.win) ? self.opt.win[0].nodeName : this.doc[0].nodeName);
+ this.haswrapper = (self.opt.win !== false);
+ this.win = self.opt.win || (this.ispage ? $(window) : this.doc);
+ this.docscroll = (this.ispage && !this.haswrapper) ? $(window) : this.win;
+ this.body = $("body");
+ this.viewport = false;
+
+ this.isfixed = false;
+
+ this.iframe = false;
+ this.isiframe = ((this.doc[0].nodeName == 'IFRAME') && (this.win[0].nodeName == 'IFRAME'));
+
+ this.istextarea = (this.win[0].nodeName == 'TEXTAREA');
+
+ this.forcescreen = false; //force to use screen position on events
+
+ this.canshowonmouseevent = (self.opt.autohidemode != "scroll");
+
+ // Events jump table
+ this.onmousedown = false;
+ this.onmouseup = false;
+ this.onmousemove = false;
+ this.onmousewheel = false;
+ this.onkeypress = false;
+ this.ongesturezoom = false;
+ this.onclick = false;
+
+ // Nicescroll custom events
+ this.onscrollstart = false;
+ this.onscrollend = false;
+ this.onscrollcancel = false;
+
+ this.onzoomin = false;
+ this.onzoomout = false;
+
+ // Let's start!
+ this.view = false;
+ this.page = false;
+
+ this.scroll = {
+ x: 0,
+ y: 0
+ };
+ this.scrollratio = {
+ x: 0,
+ y: 0
+ };
+ this.cursorheight = 20;
+ this.scrollvaluemax = 0;
+
+ this.isrtlmode = (this.opt.rtlmode == "auto") ? ((this.win[0] == window ? this.body : this.win).css("direction") == "rtl") : (this.opt.rtlmode === true);
+ // this.checkrtlmode = false;
+
+ this.scrollrunning = false;
+
+ this.scrollmom = false;
+
+ this.observer = false; // observer div changes
+ this.observerremover = false; // observer on parent for remove detection
+ this.observerbody = false; // observer on body for position change
+
+ do {
+ this.id = "ascrail" + (ascrailcounter++);
+ } while (document.getElementById(this.id));
+
+ this.rail = false;
+ this.cursor = false;
+ this.cursorfreezed = false;
+ this.selectiondrag = false;
+
+ this.zoom = false;
+ this.zoomactive = false;
+
+ this.hasfocus = false;
+ this.hasmousefocus = false;
+
+ this.visibility = true;
+ this.railslocked = false; // locked by resize
+ this.locked = false; // prevent lost of locked status sets by user
+ this.hidden = false; // rails always hidden
+ this.cursoractive = true; // user can interact with cursors
+
+ this.wheelprevented = false; //prevent mousewheel event
+
+ this.overflowx = self.opt.overflowx;
+ this.overflowy = self.opt.overflowy;
+
+ this.nativescrollingarea = false;
+ this.checkarea = 0;
+
+ this.events = []; // event list for unbind
+
+ this.saved = {}; // style saved
+
+ this.delaylist = {};
+ this.synclist = {};
+
+ this.lastdeltax = 0;
+ this.lastdeltay = 0;
+
+ this.detected = getBrowserDetection();
+
+ var cap = $.extend({}, this.detected);
+
+ this.canhwscroll = (cap.hastransform && self.opt.hwacceleration);
+ this.ishwscroll = (this.canhwscroll && self.haswrapper);
+
+ this.hasreversehr = (this.isrtlmode&&!cap.iswebkit); //RTL mode with reverse horizontal axis
+
+ this.istouchcapable = false; // desktop devices with touch screen support
+
+ //## Check WebKit-based desktop with touch support
+ //## + Firefox 18 nightly build (desktop) false positive (or desktop with touch support)
+ if (cap.cantouch && !cap.isios && !cap.isandroid && (cap.iswebkit || cap.ismozilla)) {
+ this.istouchcapable = true;
+ cap.cantouch = false; // parse normal desktop events
+ }
+
+ //## disable MouseLock API on user request
+ if (!self.opt.enablemouselockapi) {
+ cap.hasmousecapture = false;
+ cap.haspointerlock = false;
+ }
+
+/* deprecated
+ this.delayed = function(name, fn, tm, lazy) {
+ };
+*/
+
+ this.debounced = function(name, fn, tm) {
+ var dd = self.delaylist[name];
+ self.delaylist[name] = fn;
+ if (!dd) {
+ setTimeout(function() {
+ var fn = self.delaylist[name];
+ self.delaylist[name] = false;
+ fn.call(self);
+ }, tm);
+ }
+ };
+
+ var _onsync = false;
+
+ this.synched = function(name, fn) {
+
+ function requestSync() {
+ if (_onsync) return;
+ setAnimationFrame(function() {
+ _onsync = false;
+ for (var nn in self.synclist) {
+ var fn = self.synclist[nn];
+ if (fn) fn.call(self);
+ self.synclist[nn] = false;
+ }
+ });
+ _onsync = true;
+ }
+
+ self.synclist[name] = fn;
+ requestSync();
+ return name;
+ };
+
+ this.unsynched = function(name) {
+ if (self.synclist[name]) self.synclist[name] = false;
+ };
+
+ this.css = function(el, pars) { // save & set
+ for (var n in pars) {
+ self.saved.css.push([el, n, el.css(n)]);
+ el.css(n, pars[n]);
+ }
+ };
+
+ this.scrollTop = function(val) {
+ return (typeof val == "undefined") ? self.getScrollTop() : self.setScrollTop(val);
+ };
+
+ this.scrollLeft = function(val) {
+ return (typeof val == "undefined") ? self.getScrollLeft() : self.setScrollLeft(val);
+ };
+
+ // derived by by Dan Pupius www.pupius.net
+ var BezierClass = function(st, ed, spd, p1, p2, p3, p4) {
+
+ this.st = st;
+ this.ed = ed;
+ this.spd = spd;
+
+ this.p1 = p1 || 0;
+ this.p2 = p2 || 1;
+ this.p3 = p3 || 0;
+ this.p4 = p4 || 1;
+
+ this.ts = (new Date()).getTime();
+ this.df = this.ed - this.st;
+ };
+ BezierClass.prototype = {
+ B2: function(t) {
+ return 3 * t * t * (1 - t);
+ },
+ B3: function(t) {
+ return 3 * t * (1 - t) * (1 - t);
+ },
+ B4: function(t) {
+ return (1 - t) * (1 - t) * (1 - t);
+ },
+ getNow: function() {
+ var nw = (new Date()).getTime();
+ var pc = 1 - ((nw - this.ts) / this.spd);
+ var bz = this.B2(pc) + this.B3(pc) + this.B4(pc);
+ return (pc < 0) ? this.ed : this.st + Math.round(this.df * bz);
+ },
+ update: function(ed, spd) {
+ this.st = this.getNow();
+ this.ed = ed;
+ this.spd = spd;
+ this.ts = (new Date()).getTime();
+ this.df = this.ed - this.st;
+ return this;
+ }
+ };
+
+ //derived from http://stackoverflow.com/questions/11236090/
+ function getMatrixValues() {
+ var tr = self.doc.css(cap.trstyle);
+ if (tr && (tr.substr(0, 6) == "matrix")) {
+ return tr.replace(/^.*\((.*)\)$/g, "$1").replace(/px/g, '').split(/, +/);
+ }
+ return false;
+ }
+
+ if (this.ishwscroll) {
+ // hw accelerated scroll
+ this.doc.translate = {
+ x: 0,
+ y: 0,
+ tx: "0px",
+ ty: "0px"
+ };
+
+ //this one can help to enable hw accel on ios6 http://indiegamr.com/ios6-html-hardware-acceleration-changes-and-how-to-fix-them/
+ if (cap.hastranslate3d && cap.isios) this.doc.css("-webkit-backface-visibility", "hidden"); // prevent flickering http://stackoverflow.com/questions/3461441/
+
+ this.getScrollTop = function(last) {
+ if (!last) {
+ var mtx = getMatrixValues();
+ if (mtx) return (mtx.length == 16) ? -mtx[13] : -mtx[5]; //matrix3d 16 on IE10
+ if (self.timerscroll && self.timerscroll.bz) return self.timerscroll.bz.getNow();
+ }
+ return self.doc.translate.y;
+ };
+
+ this.getScrollLeft = function(last) {
+ if (!last) {
+ var mtx = getMatrixValues();
+ if (mtx) return (mtx.length == 16) ? -mtx[12] : -mtx[4]; //matrix3d 16 on IE10
+ if (self.timerscroll && self.timerscroll.bh) return self.timerscroll.bh.getNow();
+ }
+ return self.doc.translate.x;
+ };
+
+ this.notifyScrollEvent = function(el) {
+ var e = document.createEvent("UIEvents");
+ e.initUIEvent("scroll", false, true, window, 1);
+ e.niceevent = true;
+ el.dispatchEvent(e);
+ };
+
+ var cxscrollleft = (this.isrtlmode) ? 1 : -1;
+
+ if (cap.hastranslate3d && self.opt.enabletranslate3d) {
+ this.setScrollTop = function(val, silent) {
+ self.doc.translate.y = val;
+ self.doc.translate.ty = (val * -1) + "px";
+ self.doc.css(cap.trstyle, "translate3d(" + self.doc.translate.tx + "," + self.doc.translate.ty + ",0px)");
+ if (!silent) self.notifyScrollEvent(self.win[0]);
+ };
+ this.setScrollLeft = function(val, silent) {
+ self.doc.translate.x = val;
+ self.doc.translate.tx = (val * cxscrollleft) + "px";
+ self.doc.css(cap.trstyle, "translate3d(" + self.doc.translate.tx + "," + self.doc.translate.ty + ",0px)");
+ if (!silent) self.notifyScrollEvent(self.win[0]);
+ };
+ } else {
+ this.setScrollTop = function(val, silent) {
+ self.doc.translate.y = val;
+ self.doc.translate.ty = (val * -1) + "px";
+ self.doc.css(cap.trstyle, "translate(" + self.doc.translate.tx + "," + self.doc.translate.ty + ")");
+ if (!silent) self.notifyScrollEvent(self.win[0]);
+ };
+ this.setScrollLeft = function(val, silent) {
+ self.doc.translate.x = val;
+ self.doc.translate.tx = (val * cxscrollleft) + "px";
+ self.doc.css(cap.trstyle, "translate(" + self.doc.translate.tx + "," + self.doc.translate.ty + ")");
+ if (!silent) self.notifyScrollEvent(self.win[0]);
+ };
+ }
+ } else {
+ // native scroll
+ this.getScrollTop = function() {
+ return self.docscroll.scrollTop();
+ };
+ this.setScrollTop = function(val) {
+ return self.docscroll.scrollTop(val);
+ };
+ this.getScrollLeft = function() {
+ if (self.detected.ismozilla && self.isrtlmode)
+ return Math.abs(self.docscroll.scrollLeft());
+ return self.docscroll.scrollLeft();
+ };
+ this.setScrollLeft = function(val) {
+ return self.docscroll.scrollLeft((self.detected.ismozilla && self.isrtlmode) ? -val : val);
+ };
+ }
+
+ this.getTarget = function(e) {
+ if (!e) return false;
+ if (e.target) return e.target;
+ if (e.srcElement) return e.srcElement;
+ return false;
+ };
+
+ this.hasParent = function(e, id) {
+ if (!e) return false;
+ var el = e.target || e.srcElement || e || false;
+ while (el && el.id != id) {
+ el = el.parentNode || false;
+ }
+ return (el !== false);
+ };
+
+ function getZIndex() {
+ var dom = self.win;
+ if ("zIndex" in dom) return dom.zIndex(); // use jQuery UI method when available
+ while (dom.length > 0) {
+ if (dom[0].nodeType == 9) return false;
+ var zi = dom.css('zIndex');
+ if (!isNaN(zi) && zi != 0) return parseInt(zi);
+ dom = dom.parent();
+ }
+ return false;
+ }
+
+ //inspired by http://forum.jquery.com/topic/width-includes-border-width-when-set-to-thin-medium-thick-in-ie
+ var _convertBorderWidth = {
+ "thin": 1,
+ "medium": 3,
+ "thick": 5
+ };
+
+ function getWidthToPixel(dom, prop, chkheight) {
+ var wd = dom.css(prop);
+ var px = parseFloat(wd);
+ if (isNaN(px)) {
+ px = _convertBorderWidth[wd] || 0;
+ var brd = (px == 3) ? ((chkheight) ? (self.win.outerHeight() - self.win.innerHeight()) : (self.win.outerWidth() - self.win.innerWidth())) : 1; //DON'T TRUST CSS
+ if (self.isie8 && px) px += 1;
+ return (brd) ? px : 0;
+ }
+ return px;
+ }
+
+ this.getDocumentScrollOffset = function() {
+ return {top:window.pageYOffset||document.documentElement.scrollTop,
+ left:window.pageXOffset||document.documentElement.scrollLeft};
+ }
+
+ this.getOffset = function() {
+ if (self.isfixed) {
+ var ofs = self.win.offset(); // fix Chrome auto issue (when right/bottom props only)
+ var scrl = self.getDocumentScrollOffset();
+ ofs.top-=scrl.top;
+ ofs.left-=scrl.left;
+ return ofs;
+ }
+ var ww = self.win.offset();
+ if (!self.viewport) return ww;
+ var vp = self.viewport.offset();
+ return {
+ top: ww.top - vp.top,// + self.viewport.scrollTop(),
+ left: ww.left - vp.left // + self.viewport.scrollLeft()
+ };
+ };
+
+ this.updateScrollBar = function(len) {
+ if (self.ishwscroll) {
+ self.rail.css({ //**
+ height: self.win.innerHeight() - (self.opt.railpadding.top + self.opt.railpadding.bottom)
+ });
+ if (self.railh) self.railh.css({ //**
+ width: self.win.innerWidth() - (self.opt.railpadding.left + self.opt.railpadding.right)
+ });
+
+ } else {
+ var wpos = self.getOffset();
+ var pos = {
+ top: wpos.top,
+ left: wpos.left - (self.opt.railpadding.left + self.opt.railpadding.right)
+ };
+ pos.top += getWidthToPixel(self.win, 'border-top-width', true);
+ pos.left += (self.rail.align) ? self.win.outerWidth() - getWidthToPixel(self.win, 'border-right-width') - self.rail.width : getWidthToPixel(self.win, 'border-left-width');
+
+ var off = self.opt.railoffset;
+ if (off) {
+ if (off.top) pos.top += off.top;
+ if (self.rail.align && off.left) pos.left += off.left;
+ }
+
+ if (!self.railslocked) self.rail.css({
+ top: pos.top,
+ left: pos.left,
+ height: ((len) ? len.h : self.win.innerHeight()) - (self.opt.railpadding.top + self.opt.railpadding.bottom)
+ });
+
+ if (self.zoom) {
+ self.zoom.css({
+ top: pos.top + 1,
+ left: (self.rail.align == 1) ? pos.left - 20 : pos.left + self.rail.width + 4
+ });
+ }
+
+ if (self.railh && !self.railslocked) {
+ var pos = {
+ top: wpos.top,
+ left: wpos.left
+ };
+ var off = self.opt.railhoffset;
+ if (!!off) {
+ if (!!off.top) pos.top += off.top;
+ if (!!off.left) pos.left += off.left;
+ }
+ var y = (self.railh.align) ? pos.top + getWidthToPixel(self.win, 'border-top-width', true) + self.win.innerHeight() - self.railh.height : pos.top + getWidthToPixel(self.win, 'border-top-width', true);
+ var x = pos.left + getWidthToPixel(self.win, 'border-left-width');
+ self.railh.css({
+ top: y - (self.opt.railpadding.top + self.opt.railpadding.bottom),
+ left: x,
+ width: self.railh.width
+ });
+ }
+
+
+ }
+ };
+
+ this.doRailClick = function(e, dbl, hr) {
+ var fn, pg, cur, pos;
+
+ if (self.railslocked) return;
+ self.cancelEvent(e);
+
+ if (dbl) {
+ fn = (hr) ? self.doScrollLeft : self.doScrollTop;
+ cur = (hr) ? ((e.pageX - self.railh.offset().left - (self.cursorwidth / 2)) * self.scrollratio.x) : ((e.pageY - self.rail.offset().top - (self.cursorheight / 2)) * self.scrollratio.y);
+ fn(cur);
+ } else {
+ fn = (hr) ? self.doScrollLeftBy : self.doScrollBy;
+ cur = (hr) ? self.scroll.x : self.scroll.y;
+ pos = (hr) ? e.pageX - self.railh.offset().left : e.pageY - self.rail.offset().top;
+ pg = (hr) ? self.view.w : self.view.h;
+ fn((cur >= pos) ? pg: -pg);// (cur >= pos) ? fn(pg): fn(-pg);
+ }
+
+ };
+
+ self.hasanimationframe = (setAnimationFrame);
+ self.hascancelanimationframe = (clearAnimationFrame);
+
+ if (!self.hasanimationframe) {
+ setAnimationFrame = function(fn) {
+ return setTimeout(fn, 15 - Math.floor((+new Date()) / 1000) % 16);
+ }; // 1000/60)};
+ clearAnimationFrame = clearInterval;
+ } else if (!self.hascancelanimationframe) clearAnimationFrame = function() {
+ self.cancelAnimationFrame = true;
+ };
+
+ this.init = function() {
+
+ self.saved.css = [];
+
+ if (cap.isie7mobile) return true; // SORRY, DO NOT WORK!
+ if (cap.isoperamini) return true; // SORRY, DO NOT WORK!
+
+ if (cap.hasmstouch) self.css((self.ispage) ? $("html") : self.win, {
+ '-ms-touch-action': 'none'
+ });
+
+ self.zindex = "auto";
+ if (!self.ispage && self.opt.zindex == "auto") {
+ self.zindex = getZIndex() || "auto";
+ } else {
+ self.zindex = self.opt.zindex;
+ }
+
+ if (!self.ispage && self.zindex != "auto") {
+ if (self.zindex > globalmaxzindex) globalmaxzindex = self.zindex;
+ }
+
+ if (self.isie && self.zindex == 0 && self.opt.zindex == "auto") { // fix IE auto == 0
+ self.zindex = "auto";
+ }
+
+ if (!self.ispage || (!cap.cantouch && !cap.isieold && !cap.isie9mobile)) {
+
+ var cont = self.docscroll;
+ if (self.ispage) cont = (self.haswrapper) ? self.win : self.doc;
+
+ if (!cap.isie9mobile) self.css(cont, {
+ 'overflow-y': 'hidden'
+ });
+
+ if (self.ispage && cap.isie7) {
+ if (self.doc[0].nodeName == 'BODY') self.css($("html"), {
+ 'overflow-y': 'hidden'
+ }); //IE7 double scrollbar issue
+ else if (self.doc[0].nodeName == 'HTML') self.css($("body"), {
+ 'overflow-y': 'hidden'
+ }); //IE7 double scrollbar issue
+ }
+
+ if (cap.isios && !self.ispage && !self.haswrapper) self.css($("body"), {
+ "-webkit-overflow-scrolling": "touch"
+ }); //force hw acceleration
+
+ var cursor = $(document.createElement('div'));
+ cursor.css({
+ position: "relative",
+ top: 0,
+ "float": "right",
+ width: self.opt.cursorwidth,
+ height: "0px",
+ 'background-color': self.opt.cursorcolor,
+ border: self.opt.cursorborder,
+ 'background-clip': 'padding-box',
+ '-webkit-border-radius': self.opt.cursorborderradius,
+ '-moz-border-radius': self.opt.cursorborderradius,
+ 'border-radius': self.opt.cursorborderradius
+ });
+
+ cursor.hborder = parseFloat(cursor.outerHeight() - cursor.innerHeight());
+
+ cursor.addClass('nicescroll-cursors');
+
+ self.cursor = cursor;
+
+ var rail = $(document.createElement('div'));
+ rail.attr('id', self.id);
+ rail.addClass('nicescroll-rails nicescroll-rails-vr');
+
+ var v, a, kp = ["left","right","top","bottom"]; //**
+ for (var n in kp) {
+ a = kp[n];
+ v = self.opt.railpadding[a];
+ (v) ? rail.css("padding-"+a,v+"px") : self.opt.railpadding[a] = 0;
+ }
+
+ rail.append(cursor);
+
+ rail.width = Math.max(parseFloat(self.opt.cursorwidth), cursor.outerWidth());
+ rail.css({
+ width: rail.width + "px",
+ 'zIndex': self.zindex,
+ "background": self.opt.background,
+ cursor: "default"
+ });
+
+ rail.visibility = true;
+ rail.scrollable = true;
+
+ rail.align = (self.opt.railalign == "left") ? 0 : 1;
+
+ self.rail = rail;
+
+ self.rail.drag = false;
+
+ var zoom = false;
+ if (self.opt.boxzoom && !self.ispage && !cap.isieold) {
+ zoom = document.createElement('div');
+
+ self.bind(zoom, "click", self.doZoom);
+ self.bind(zoom, "mouseenter", function() {
+ self.zoom.css('opacity', self.opt.cursoropacitymax);
+ });
+ self.bind(zoom, "mouseleave", function() {
+ self.zoom.css('opacity', self.opt.cursoropacitymin);
+ });
+
+ self.zoom = $(zoom);
+ self.zoom.css({
+ "cursor": "pointer",
+ 'z-index': self.zindex,
+ 'backgroundImage': 'url(' + self.opt.scriptpath + 'zoomico.png)',
+ 'height': 18,
+ 'width': 18,
+ 'backgroundPosition': '0px 0px'
+ });
+ if (self.opt.dblclickzoom) self.bind(self.win, "dblclick", self.doZoom);
+ if (cap.cantouch && self.opt.gesturezoom) {
+ self.ongesturezoom = function(e) {
+ if (e.scale > 1.5) self.doZoomIn(e);
+ if (e.scale < 0.8) self.doZoomOut(e);
+ return self.cancelEvent(e);
+ };
+ self.bind(self.win, "gestureend", self.ongesturezoom);
+ }
+ }
+
+ // init HORIZ
+
+ self.railh = false;
+ var railh;
+
+ if (self.opt.horizrailenabled) {
+
+ self.css(cont, {
+ 'overflow-x': 'hidden'
+ });
+
+ var cursor = $(document.createElement('div'));
+ cursor.css({
+ position: "absolute",
+ top: 0,
+ height: self.opt.cursorwidth,
+ width: "0px",
+ 'background-color': self.opt.cursorcolor,
+ border: self.opt.cursorborder,
+ 'background-clip': 'padding-box',
+ '-webkit-border-radius': self.opt.cursorborderradius,
+ '-moz-border-radius': self.opt.cursorborderradius,
+ 'border-radius': self.opt.cursorborderradius
+ });
+
+ if (cap.isieold) cursor.css({'overflow':'hidden'}); //IE6 horiz scrollbar issue
+
+ cursor.wborder = parseFloat(cursor.outerWidth() - cursor.innerWidth());
+
+ cursor.addClass('nicescroll-cursors');
+
+ self.cursorh = cursor;
+
+ railh = $(document.createElement('div'));
+ railh.attr('id', self.id + '-hr');
+ railh.addClass('nicescroll-rails nicescroll-rails-hr');
+ railh.height = Math.max(parseFloat(self.opt.cursorwidth), cursor.outerHeight());
+ railh.css({
+ height: railh.height + "px",
+ 'zIndex': self.zindex,
+ "background": self.opt.background
+ });
+
+ railh.append(cursor);
+
+ railh.visibility = true;
+ railh.scrollable = true;
+
+ railh.align = (self.opt.railvalign == "top") ? 0 : 1;
+
+ self.railh = railh;
+
+ self.railh.drag = false;
+
+ }
+
+ //
+
+ if (self.ispage) {
+ rail.css({
+ position: "fixed",
+ top: "0px",
+ height: "100%"
+ });
+ (rail.align) ? rail.css({
+ right: "0px"
+ }): rail.css({
+ left: "0px"
+ });
+ self.body.append(rail);
+ if (self.railh) {
+ railh.css({
+ position: "fixed",
+ left: "0px",
+ width: "100%"
+ });
+ (railh.align) ? railh.css({
+ bottom: "0px"
+ }): railh.css({
+ top: "0px"
+ });
+ self.body.append(railh);
+ }
+ } else {
+ if (self.ishwscroll) {
+ if (self.win.css('position') == 'static') self.css(self.win, {
+ 'position': 'relative'
+ });
+ var bd = (self.win[0].nodeName == 'HTML') ? self.body : self.win;
+ $(bd).scrollTop(0).scrollLeft(0); // fix rail position if content already scrolled
+ if (self.zoom) {
+ self.zoom.css({
+ position: "absolute",
+ top: 1,
+ right: 0,
+ "margin-right": rail.width + 4
+ });
+ bd.append(self.zoom);
+ }
+ rail.css({
+ position: "absolute",
+ top: 0
+ });
+ (rail.align) ? rail.css({
+ right: 0
+ }): rail.css({
+ left: 0
+ });
+ bd.append(rail);
+ if (railh) {
+ railh.css({
+ position: "absolute",
+ left: 0,
+ bottom: 0
+ });
+ (railh.align) ? railh.css({
+ bottom: 0
+ }): railh.css({
+ top: 0
+ });
+ bd.append(railh);
+ }
+ } else {
+ self.isfixed = (self.win.css("position") == "fixed");
+ var rlpos = (self.isfixed) ? "fixed" : "absolute";
+
+ if (!self.isfixed) self.viewport = self.getViewport(self.win[0]);
+ if (self.viewport) {
+ self.body = self.viewport;
+ if ((/fixed|absolute/.test(self.viewport.css("position"))) == false) self.css(self.viewport, {
+ "position": "relative"
+ });
+ }
+
+ rail.css({
+ position: rlpos
+ });
+ if (self.zoom) self.zoom.css({
+ position: rlpos
+ });
+ self.updateScrollBar();
+ self.body.append(rail);
+ if (self.zoom) self.body.append(self.zoom);
+ if (self.railh) {
+ railh.css({
+ position: rlpos
+ });
+ self.body.append(railh);
+ }
+ }
+
+ if (cap.isios) self.css(self.win, {
+ '-webkit-tap-highlight-color': 'rgba(0,0,0,0)',
+ '-webkit-touch-callout': 'none'
+ }); // prevent grey layer on click
+
+ if (cap.isie && self.opt.disableoutline) self.win.attr("hideFocus", "true"); // IE, prevent dotted rectangle on focused div
+ if (cap.iswebkit && self.opt.disableoutline) self.win.css({"outline": "none"}); // Webkit outline
+ //if (cap.isopera&&self.opt.disableoutline) self.win.css({"outline":"0"}); // Opera 12- to test [TODO]
+
+ }
+
+ if (self.opt.autohidemode === false) {
+ self.autohidedom = false;
+ self.rail.css({
+ opacity: self.opt.cursoropacitymax
+ });
+ if (self.railh) self.railh.css({
+ opacity: self.opt.cursoropacitymax
+ });
+ } else if ((self.opt.autohidemode === true) || (self.opt.autohidemode === "leave")) {
+ self.autohidedom = $().add(self.rail);
+ if (cap.isie8) self.autohidedom = self.autohidedom.add(self.cursor);
+ if (self.railh) self.autohidedom = self.autohidedom.add(self.railh);
+ if (self.railh && cap.isie8) self.autohidedom = self.autohidedom.add(self.cursorh);
+ } else if (self.opt.autohidemode == "scroll") {
+ self.autohidedom = $().add(self.rail);
+ if (self.railh) self.autohidedom = self.autohidedom.add(self.railh);
+ } else if (self.opt.autohidemode == "cursor") {
+ self.autohidedom = $().add(self.cursor);
+ if (self.railh) self.autohidedom = self.autohidedom.add(self.cursorh);
+ } else if (self.opt.autohidemode == "hidden") {
+ self.autohidedom = false;
+ self.hide();
+ self.railslocked = false;
+ }
+
+ if (cap.isie9mobile) {
+
+ self.scrollmom = new ScrollMomentumClass2D(self);
+
+ self.onmangotouch = function() {
+ var py = self.getScrollTop();
+ var px = self.getScrollLeft();
+
+ if ((py == self.scrollmom.lastscrolly) && (px == self.scrollmom.lastscrollx)) return true;
+
+ var dfy = py - self.mangotouch.sy;
+ var dfx = px - self.mangotouch.sx;
+ var df = Math.round(Math.sqrt(Math.pow(dfx, 2) + Math.pow(dfy, 2)));
+ if (df == 0) return;
+
+ var dry = (dfy < 0) ? -1 : 1;
+ var drx = (dfx < 0) ? -1 : 1;
+
+ var tm = +new Date();
+ if (self.mangotouch.lazy) clearTimeout(self.mangotouch.lazy);
+
+ if (((tm - self.mangotouch.tm) > 80) || (self.mangotouch.dry != dry) || (self.mangotouch.drx != drx)) {
+ self.scrollmom.stop();
+ self.scrollmom.reset(px, py);
+ self.mangotouch.sy = py;
+ self.mangotouch.ly = py;
+ self.mangotouch.sx = px;
+ self.mangotouch.lx = px;
+ self.mangotouch.dry = dry;
+ self.mangotouch.drx = drx;
+ self.mangotouch.tm = tm;
+ } else {
+
+ self.scrollmom.stop();
+ self.scrollmom.update(self.mangotouch.sx - dfx, self.mangotouch.sy - dfy);
+ self.mangotouch.tm = tm;
+
+ var ds = Math.max(Math.abs(self.mangotouch.ly - py), Math.abs(self.mangotouch.lx - px));
+ self.mangotouch.ly = py;
+ self.mangotouch.lx = px;
+
+ if (ds > 2) {
+ self.mangotouch.lazy = setTimeout(function() {
+ self.mangotouch.lazy = false;
+ self.mangotouch.dry = 0;
+ self.mangotouch.drx = 0;
+ self.mangotouch.tm = 0;
+ self.scrollmom.doMomentum(30);
+ }, 100);
+ }
+ }
+ };
+
+ var top = self.getScrollTop();
+ var lef = self.getScrollLeft();
+ self.mangotouch = {
+ sy: top,
+ ly: top,
+ dry: 0,
+ sx: lef,
+ lx: lef,
+ drx: 0,
+ lazy: false,
+ tm: 0
+ };
+
+ self.bind(self.docscroll, "scroll", self.onmangotouch);
+
+ } else {
+
+ if (cap.cantouch || self.istouchcapable || self.opt.touchbehavior || cap.hasmstouch) {
+
+ self.scrollmom = new ScrollMomentumClass2D(self);
+
+ self.ontouchstart = function(e) {
+ if (e.pointerType && e.pointerType != 2 && e.pointerType != "touch") return false;
+
+ self.hasmoving = false;
+
+ if (!self.railslocked) {
+
+ var tg;
+ if (cap.hasmstouch) {
+ tg = (e.target) ? e.target : false;
+ while (tg) {
+ var nc = $(tg).getNiceScroll();
+ if ((nc.length > 0) && (nc[0].me == self.me)) break;
+ if (nc.length > 0) return false;
+ if ((tg.nodeName == 'DIV') && (tg.id == self.id)) break;
+ tg = (tg.parentNode) ? tg.parentNode : false;
+ }
+ }
+
+ self.cancelScroll();
+
+ tg = self.getTarget(e);
+
+ if (tg) {
+ var skp = (/INPUT/i.test(tg.nodeName)) && (/range/i.test(tg.type));
+ if (skp) return self.stopPropagation(e);
+ }
+
+ if (!("clientX" in e) && ("changedTouches" in e)) {
+ e.clientX = e.changedTouches[0].clientX;
+ e.clientY = e.changedTouches[0].clientY;
+ }
+
+ if (self.forcescreen) {
+ var le = e;
+ e = {
+ "original": (e.original) ? e.original : e
+ };
+ e.clientX = le.screenX;
+ e.clientY = le.screenY;
+ }
+
+ self.rail.drag = {
+ x: e.clientX,
+ y: e.clientY,
+ sx: self.scroll.x,
+ sy: self.scroll.y,
+ st: self.getScrollTop(),
+ sl: self.getScrollLeft(),
+ pt: 2,
+ dl: false
+ };
+
+ if (self.ispage || !self.opt.directionlockdeadzone) {
+ self.rail.drag.dl = "f";
+ } else {
+
+ var view = {
+ w: $(window).width(),
+ h: $(window).height()
+ };
+
+ var page = {
+ w: Math.max(document.body.scrollWidth, document.documentElement.scrollWidth),
+ h: Math.max(document.body.scrollHeight, document.documentElement.scrollHeight)
+ };
+
+ var maxh = Math.max(0, page.h - view.h);
+ var maxw = Math.max(0, page.w - view.w);
+
+ if (!self.rail.scrollable && self.railh.scrollable) self.rail.drag.ck = (maxh > 0) ? "v" : false;
+ else if (self.rail.scrollable && !self.railh.scrollable) self.rail.drag.ck = (maxw > 0) ? "h" : false;
+ else self.rail.drag.ck = false;
+ if (!self.rail.drag.ck) self.rail.drag.dl = "f";
+ }
+
+ if (self.opt.touchbehavior && self.isiframe && cap.isie) {
+ var wp = self.win.position();
+ self.rail.drag.x += wp.left;
+ self.rail.drag.y += wp.top;
+ }
+
+ self.hasmoving = false;
+ self.lastmouseup = false;
+ self.scrollmom.reset(e.clientX, e.clientY);
+
+ if (!cap.cantouch && !this.istouchcapable && !e.pointerType) {
+
+ var ip = (tg) ? /INPUT|SELECT|TEXTAREA/i.test(tg.nodeName) : false;
+ if (!ip) {
+ if (!self.ispage && cap.hasmousecapture) tg.setCapture();
+ if (self.opt.touchbehavior) {
+ if (tg.onclick && !(tg._onclick || false)) { // intercept DOM0 onclick event
+ tg._onclick = tg.onclick;
+ tg.onclick = function(e) {
+ if (self.hasmoving) return false;
+ tg._onclick.call(this, e);
+ };
+ }
+ return self.cancelEvent(e);
+ }
+ return self.stopPropagation(e);
+ }
+
+ if (/SUBMIT|CANCEL|BUTTON/i.test($(tg).attr('type'))) {
+ pc = {
+ "tg": tg,
+ "click": false
+ };
+ self.preventclick = pc;
+ }
+
+ }
+ }
+
+ };
+
+ self.ontouchend = function(e) {
+ if (!self.rail.drag) return true;
+ if (self.rail.drag.pt == 2) {
+ if (e.pointerType && e.pointerType != 2 && e.pointerType != "touch") return false;
+ self.scrollmom.doMomentum();
+ self.rail.drag = false;
+ if (self.hasmoving) {
+ self.lastmouseup = true;
+ self.hideCursor();
+ if (cap.hasmousecapture) document.releaseCapture();
+ if (!cap.cantouch) return self.cancelEvent(e);
+ }
+ }
+ else if (self.rail.drag.pt == 1) {
+ return self.onmouseup(e);
+ }
+
+ };
+
+ var moveneedoffset = (self.opt.touchbehavior && self.isiframe && !cap.hasmousecapture);
+
+ self.ontouchmove = function(e, byiframe) {
+
+ if (!self.rail.drag) return false;
+
+ if (e.targetTouches && self.opt.preventmultitouchscrolling) {
+ if (e.targetTouches.length > 1) return false; // multitouch
+ }
+
+ if (e.pointerType && e.pointerType != 2 && e.pointerType != "touch") return false;
+
+ if (self.rail.drag.pt == 2) {
+ if (cap.cantouch && (cap.isios) && (typeof e.original == "undefined")) return true; // prevent ios "ghost" events by clickable elements
+
+ self.hasmoving = true;
+
+ if (self.preventclick && !self.preventclick.click) {
+ self.preventclick.click = self.preventclick.tg.onclick || false;
+ self.preventclick.tg.onclick = self.onpreventclick;
+ }
+
+ var ev = $.extend({
+ "original": e
+ }, e);
+ e = ev;
+
+ if (("changedTouches" in e)) {
+ e.clientX = e.changedTouches[0].clientX;
+ e.clientY = e.changedTouches[0].clientY;
+ }
+
+ if (self.forcescreen) {
+ var le = e;
+ e = {
+ "original": (e.original) ? e.original : e
+ };
+ e.clientX = le.screenX;
+ e.clientY = le.screenY;
+ }
+
+ var ofy,ofx;
+ ofx = ofy = 0;
+
+ if (moveneedoffset && !byiframe) {
+ var wp = self.win.position();
+ ofx = -wp.left;
+ ofy = -wp.top;
+ }
+
+ var fy = e.clientY + ofy;
+ var my = (fy - self.rail.drag.y);
+ var fx = e.clientX + ofx;
+ var mx = (fx - self.rail.drag.x);
+
+ var ny = self.rail.drag.st - my;
+
+ if (self.ishwscroll && self.opt.bouncescroll) {
+ if (ny < 0) {
+ ny = Math.round(ny / 2);
+ // fy = 0;
+ } else if (ny > self.page.maxh) {
+ ny = self.page.maxh + Math.round((ny - self.page.maxh) / 2);
+ // fy = 0;
+ }
+ } else {
+ if (ny < 0) {
+ ny = 0;
+ fy = 0;
+ }
+ if (ny > self.page.maxh) {
+ ny = self.page.maxh;
+ fy = 0;
+ }
+ }
+
+ var nx;
+ if (self.railh && self.railh.scrollable) {
+ nx = (self.isrtlmode) ? mx - self.rail.drag.sl : self.rail.drag.sl - mx;
+
+ if (self.ishwscroll && self.opt.bouncescroll) {
+ if (nx < 0) {
+ nx = Math.round(nx / 2);
+ // fx = 0;
+ } else if (nx > self.page.maxw) {
+ nx = self.page.maxw + Math.round((nx - self.page.maxw) / 2);
+ // fx = 0;
+ }
+ } else {
+ if (nx < 0) {
+ nx = 0;
+ fx = 0;
+ }
+ if (nx > self.page.maxw) {
+ nx = self.page.maxw;
+ fx = 0;
+ }
+ }
+
+ }
+
+ var grabbed = false;
+ if (self.rail.drag.dl) {
+ grabbed = true;
+ if (self.rail.drag.dl == "v") nx = self.rail.drag.sl;
+ else if (self.rail.drag.dl == "h") ny = self.rail.drag.st;
+ } else {
+ var ay = Math.abs(my);
+ var ax = Math.abs(mx);
+ var dz = self.opt.directionlockdeadzone;
+ if (self.rail.drag.ck == "v") {
+ if (ay > dz && (ax <= (ay * 0.3))) {
+ self.rail.drag = false;
+ return true;
+ } else if (ax > dz) {
+ self.rail.drag.dl = "f";
+ $("body").scrollTop($("body").scrollTop()); // stop iOS native scrolling (when active javascript has blocked)
+ }
+ } else if (self.rail.drag.ck == "h") {
+ if (ax > dz && (ay <= (ax * 0.3))) {
+ self.rail.drag = false;
+ return true;
+ } else if (ay > dz) {
+ self.rail.drag.dl = "f";
+ $("body").scrollLeft($("body").scrollLeft()); // stop iOS native scrolling (when active javascript has blocked)
+ }
+ }
+ }
+
+ self.synched("touchmove", function() {
+ if (self.rail.drag && (self.rail.drag.pt == 2)) {
+ if (self.prepareTransition) self.prepareTransition(0);
+ if (self.rail.scrollable) self.setScrollTop(ny);
+ self.scrollmom.update(fx, fy);
+ if (self.railh && self.railh.scrollable) {
+ self.setScrollLeft(nx);
+ self.showCursor(ny, nx);
+ } else {
+ self.showCursor(ny);
+ }
+ if (cap.isie10) document.selection.clear();
+ }
+ });
+
+ if (cap.ischrome && self.istouchcapable) grabbed = false; //chrome touch emulation doesn't like!
+ if (grabbed) return self.cancelEvent(e);
+ }
+ else if (self.rail.drag.pt == 1) { // drag on cursor
+ return self.onmousemove(e);
+ }
+
+ };
+
+ }
+
+ self.onmousedown = function(e, hronly) {
+ if (self.rail.drag && self.rail.drag.pt != 1) return;
+ if (self.railslocked) return self.cancelEvent(e);
+ self.cancelScroll();
+ self.rail.drag = {
+ x: e.clientX,
+ y: e.clientY,
+ sx: self.scroll.x,
+ sy: self.scroll.y,
+ pt: 1,
+ hr: (!!hronly)
+ };
+ var tg = self.getTarget(e);
+ if (!self.ispage && cap.hasmousecapture) tg.setCapture();
+ if (self.isiframe && !cap.hasmousecapture) {
+ self.saved.csspointerevents = self.doc.css("pointer-events");
+ self.css(self.doc, {
+ "pointer-events": "none"
+ });
+ }
+ self.hasmoving = false;
+ return self.cancelEvent(e);
+ };
+
+ self.onmouseup = function(e) {
+ if (self.rail.drag) {
+ if (self.rail.drag.pt != 1) return true;
+ if (cap.hasmousecapture) document.releaseCapture();
+ if (self.isiframe && !cap.hasmousecapture) self.doc.css("pointer-events", self.saved.csspointerevents);
+ self.rail.drag = false;
+ //if (!self.rail.active) self.hideCursor();
+ if (self.hasmoving) self.triggerScrollEnd(); // TODO - check &&!self.scrollrunning
+ return self.cancelEvent(e);
+ }
+ };
+
+ self.onmousemove = function(e) {
+ if (self.rail.drag) {
+ if (self.rail.drag.pt != 1) return;
+
+ if (cap.ischrome && e.which == 0) return self.onmouseup(e);
+
+ self.cursorfreezed = true;
+ self.hasmoving = true;
+
+ if (self.rail.drag.hr) {
+ self.scroll.x = self.rail.drag.sx + (e.clientX - self.rail.drag.x);
+ if (self.scroll.x < 0) self.scroll.x = 0;
+ var mw = self.scrollvaluemaxw;
+ if (self.scroll.x > mw) self.scroll.x = mw;
+ } else {
+ self.scroll.y = self.rail.drag.sy + (e.clientY - self.rail.drag.y);
+ if (self.scroll.y < 0) self.scroll.y = 0;
+ var my = self.scrollvaluemax;
+ if (self.scroll.y > my) self.scroll.y = my;
+ }
+
+ self.synched('mousemove', function() {
+ if (self.rail.drag && (self.rail.drag.pt == 1)) {
+ self.showCursor();
+ if (self.rail.drag.hr) {
+ if (self.hasreversehr) {
+ self.doScrollLeft(self.scrollvaluemaxw-Math.round(self.scroll.x * self.scrollratio.x), self.opt.cursordragspeed);
+ } else {
+ self.doScrollLeft(Math.round(self.scroll.x * self.scrollratio.x), self.opt.cursordragspeed);
+ }
+ }
+ else self.doScrollTop(Math.round(self.scroll.y * self.scrollratio.y), self.opt.cursordragspeed);
+ }
+ });
+
+ return self.cancelEvent(e);
+ }
+ /*
+ else {
+ self.checkarea = true;
+ }
+*/
+ };
+
+ if (cap.cantouch || self.opt.touchbehavior) {
+
+ self.onpreventclick = function(e) {
+ if (self.preventclick) {
+ self.preventclick.tg.onclick = self.preventclick.click;
+ self.preventclick = false;
+ return self.cancelEvent(e);
+ }
+ }
+
+ self.bind(self.win, "mousedown", self.ontouchstart); // control content dragging
+
+ self.onclick = (cap.isios) ? false : function(e) {
+ if (self.lastmouseup) {
+ self.lastmouseup = false;
+ return self.cancelEvent(e);
+ } else {
+ return true;
+ }
+ };
+
+ if (self.opt.grabcursorenabled && cap.cursorgrabvalue) {
+ self.css((self.ispage) ? self.doc : self.win, {
+ 'cursor': cap.cursorgrabvalue
+ });
+ self.css(self.rail, {
+ 'cursor': cap.cursorgrabvalue
+ });
+ }
+
+ } else {
+
+ var checkSelectionScroll = function(e) {
+ if (!self.selectiondrag) return;
+
+ if (e) {
+ var ww = self.win.outerHeight();
+ var df = (e.pageY - self.selectiondrag.top);
+ if (df > 0 && df < ww) df = 0;
+ if (df >= ww) df -= ww;
+ self.selectiondrag.df = df;
+ }
+ if (self.selectiondrag.df == 0) return;
+
+ var rt = -Math.floor(self.selectiondrag.df / 6) * 2;
+ self.doScrollBy(rt);
+
+ self.debounced("doselectionscroll", function() {
+ checkSelectionScroll()
+ }, 50);
+ };
+
+ if ("getSelection" in document) { // A grade - Major browsers
+ self.hasTextSelected = function() {
+ return (document.getSelection().rangeCount > 0);
+ };
+ } else if ("selection" in document) { //IE9-
+ self.hasTextSelected = function() {
+ return (document.selection.type != "None");
+ };
+ } else {
+ self.hasTextSelected = function() { // no support
+ return false;
+ };
+ }
+
+ self.onselectionstart = function(e) {
+/* More testing - severe chrome issues
+ if (!self.haswrapper&&(e.which&&e.which==2)) { // fool browser to manage middle button scrolling
+ self.win.css({'overflow':'auto'});
+ setTimeout(function(){
+ self.win.css({'overflow':''});
+ },10);
+ return true;
+ }
+*/
+ if (self.ispage) return;
+ self.selectiondrag = self.win.offset();
+ };
+
+ self.onselectionend = function(e) {
+ self.selectiondrag = false;
+ };
+ self.onselectiondrag = function(e) {
+ if (!self.selectiondrag) return;
+ if (self.hasTextSelected()) self.debounced("selectionscroll", function() {
+ checkSelectionScroll(e)
+ }, 250);
+ };
+
+
+ }
+
+ if (cap.hasw3ctouch) { //IE11+
+ self.css(self.rail, {
+ 'touch-action': 'none'
+ });
+ self.css(self.cursor, {
+ 'touch-action': 'none'
+ });
+ self.bind(self.win, "pointerdown", self.ontouchstart);
+ self.bind(document, "pointerup", self.ontouchend);
+ self.bind(document, "pointermove", self.ontouchmove);
+ } else if (cap.hasmstouch) { //IE10
+ self.css(self.rail, {
+ '-ms-touch-action': 'none'
+ });
+ self.css(self.cursor, {
+ '-ms-touch-action': 'none'
+ });
+ self.bind(self.win, "MSPointerDown", self.ontouchstart);
+ self.bind(document, "MSPointerUp", self.ontouchend);
+ self.bind(document, "MSPointerMove", self.ontouchmove);
+ self.bind(self.cursor, "MSGestureHold", function(e) {
+ e.preventDefault()
+ });
+ self.bind(self.cursor, "contextmenu", function(e) {
+ e.preventDefault()
+ });
+ } else if (this.istouchcapable) { //desktop with screen touch enabled
+ self.bind(self.win, "touchstart", self.ontouchstart);
+ self.bind(document, "touchend", self.ontouchend);
+ self.bind(document, "touchcancel", self.ontouchend);
+ self.bind(document, "touchmove", self.ontouchmove);
+ }
+
+
+ if (self.opt.cursordragontouch || (!cap.cantouch && !self.opt.touchbehavior)) {
+
+ self.rail.css({
+ "cursor": "default"
+ });
+ self.railh && self.railh.css({
+ "cursor": "default"
+ });
+
+ self.jqbind(self.rail, "mouseenter", function() {
+ if (!self.ispage && !self.win.is(":visible")) return false;
+ if (self.canshowonmouseevent) self.showCursor();
+ self.rail.active = true;
+ });
+ self.jqbind(self.rail, "mouseleave", function() {
+ self.rail.active = false;
+ if (!self.rail.drag) self.hideCursor();
+ });
+
+ if (self.opt.sensitiverail) {
+ self.bind(self.rail, "click", function(e) {
+ self.doRailClick(e, false, false)
+ });
+ self.bind(self.rail, "dblclick", function(e) {
+ self.doRailClick(e, true, false)
+ });
+ self.bind(self.cursor, "click", function(e) {
+ self.cancelEvent(e)
+ });
+ self.bind(self.cursor, "dblclick", function(e) {
+ self.cancelEvent(e)
+ });
+ }
+
+ if (self.railh) {
+ self.jqbind(self.railh, "mouseenter", function() {
+ if (!self.ispage && !self.win.is(":visible")) return false;
+ if (self.canshowonmouseevent) self.showCursor();
+ self.rail.active = true;
+ });
+ self.jqbind(self.railh, "mouseleave", function() {
+ self.rail.active = false;
+ if (!self.rail.drag) self.hideCursor();
+ });
+
+ if (self.opt.sensitiverail) {
+ self.bind(self.railh, "click", function(e) {
+ self.doRailClick(e, false, true)
+ });
+ self.bind(self.railh, "dblclick", function(e) {
+ self.doRailClick(e, true, true)
+ });
+ self.bind(self.cursorh, "click", function(e) {
+ self.cancelEvent(e)
+ });
+ self.bind(self.cursorh, "dblclick", function(e) {
+ self.cancelEvent(e)
+ });
+ }
+
+ }
+
+ }
+
+ if (!cap.cantouch && !self.opt.touchbehavior) {
+
+ self.bind((cap.hasmousecapture) ? self.win : document, "mouseup", self.onmouseup);
+ self.bind(document, "mousemove", self.onmousemove);
+ if (self.onclick) self.bind(document, "click", self.onclick);
+
+ self.bind(self.cursor, "mousedown", self.onmousedown);
+ self.bind(self.cursor, "mouseup", self.onmouseup);
+
+ if (self.railh) {
+ self.bind(self.cursorh, "mousedown", function(e) {
+ self.onmousedown(e, true)
+ });
+ self.bind(self.cursorh, "mouseup", self.onmouseup);
+ }
+
+ if (!self.ispage && self.opt.enablescrollonselection) {
+ self.bind(self.win[0], "mousedown", self.onselectionstart);
+ self.bind(document, "mouseup", self.onselectionend);
+ self.bind(self.cursor, "mouseup", self.onselectionend);
+ if (self.cursorh) self.bind(self.cursorh, "mouseup", self.onselectionend);
+ self.bind(document, "mousemove", self.onselectiondrag);
+ }
+
+ if (self.zoom) {
+ self.jqbind(self.zoom, "mouseenter", function() {
+ if (self.canshowonmouseevent) self.showCursor();
+ self.rail.active = true;
+ });
+ self.jqbind(self.zoom, "mouseleave", function() {
+ self.rail.active = false;
+ if (!self.rail.drag) self.hideCursor();
+ });
+ }
+
+ } else {
+
+ self.bind((cap.hasmousecapture) ? self.win : document, "mouseup", self.ontouchend);
+ self.bind(document, "mousemove", self.ontouchmove);
+ if (self.onclick) self.bind(document, "click", self.onclick);
+
+ if (self.opt.cursordragontouch) {
+ self.bind(self.cursor, "mousedown", self.onmousedown);
+ self.bind(self.cursor, "mouseup", self.onmouseup);
+ //self.bind(self.cursor, "mousemove", self.onmousemove);
+ self.cursorh && self.bind(self.cursorh, "mousedown", function(e) {
+ self.onmousedown(e, true)
+ });
+ //self.cursorh && self.bind(self.cursorh, "mousemove", self.onmousemove);
+ self.cursorh && self.bind(self.cursorh, "mouseup", self.onmouseup);
+ }
+
+ }
+
+ if (self.opt.enablemousewheel) {
+ if (!self.isiframe) self.bind((cap.isie && self.ispage) ? document : self.win /*self.docscroll*/ , "mousewheel", self.onmousewheel);
+ self.bind(self.rail, "mousewheel", self.onmousewheel);
+ if (self.railh) self.bind(self.railh, "mousewheel", self.onmousewheelhr);
+ }
+
+ if (!self.ispage && !cap.cantouch && !(/HTML|^BODY/.test(self.win[0].nodeName))) {
+ if (!self.win.attr("tabindex")) self.win.attr({
+ "tabindex": tabindexcounter++
+ });
+
+ self.jqbind(self.win, "focus", function(e) {
+ domfocus = (self.getTarget(e)).id || true;
+ self.hasfocus = true;
+ if (self.canshowonmouseevent) self.noticeCursor();
+ });
+ self.jqbind(self.win, "blur", function(e) {
+ domfocus = false;
+ self.hasfocus = false;
+ });
+
+ self.jqbind(self.win, "mouseenter", function(e) {
+ mousefocus = (self.getTarget(e)).id || true;
+ self.hasmousefocus = true;
+ if (self.canshowonmouseevent) self.noticeCursor();
+ });
+ self.jqbind(self.win, "mouseleave", function() {
+ mousefocus = false;
+ self.hasmousefocus = false;
+ if (!self.rail.drag) self.hideCursor();
+ });
+
+ }
+
+ } // !ie9mobile
+
+ //Thanks to http://www.quirksmode.org !!
+ self.onkeypress = function(e) {
+ if (self.railslocked && self.page.maxh == 0) return true;
+
+ e = (e) ? e : window.e;
+ var tg = self.getTarget(e);
+ if (tg && /INPUT|TEXTAREA|SELECT|OPTION/.test(tg.nodeName)) {
+ var tp = tg.getAttribute('type') || tg.type || false;
+ if ((!tp) || !(/submit|button|cancel/i.tp)) return true;
+ }
+
+ if ($(tg).attr('contenteditable')) return true;
+
+ if (self.hasfocus || (self.hasmousefocus && !domfocus) || (self.ispage && !domfocus && !mousefocus)) {
+ var key = e.keyCode;
+
+ if (self.railslocked && key != 27) return self.cancelEvent(e);
+
+ var ctrl = e.ctrlKey || false;
+ var shift = e.shiftKey || false;
+
+ var ret = false;
+ switch (key) {
+ case 38:
+ case 63233: //safari
+ self.doScrollBy(24 * 3);
+ ret = true;
+ break;
+ case 40:
+ case 63235: //safari
+ self.doScrollBy(-24 * 3);
+ ret = true;
+ break;
+ case 37:
+ case 63232: //safari
+ if (self.railh) {
+ (ctrl) ? self.doScrollLeft(0): self.doScrollLeftBy(24 * 3);
+ ret = true;
+ }
+ break;
+ case 39:
+ case 63234: //safari
+ if (self.railh) {
+ (ctrl) ? self.doScrollLeft(self.page.maxw): self.doScrollLeftBy(-24 * 3);
+ ret = true;
+ }
+ break;
+ case 33:
+ case 63276: // safari
+ self.doScrollBy(self.view.h);
+ ret = true;
+ break;
+ case 34:
+ case 63277: // safari
+ self.doScrollBy(-self.view.h);
+ ret = true;
+ break;
+ case 36:
+ case 63273: // safari
+ (self.railh && ctrl) ? self.doScrollPos(0, 0): self.doScrollTo(0);
+ ret = true;
+ break;
+ case 35:
+ case 63275: // safari
+ (self.railh && ctrl) ? self.doScrollPos(self.page.maxw, self.page.maxh): self.doScrollTo(self.page.maxh);
+ ret = true;
+ break;
+ case 32:
+ if (self.opt.spacebarenabled) {
+ (shift) ? self.doScrollBy(self.view.h): self.doScrollBy(-self.view.h);
+ ret = true;
+ }
+ break;
+ case 27: // ESC
+ if (self.zoomactive) {
+ self.doZoom();
+ ret = true;
+ }
+ break;
+ }
+ if (ret) return self.cancelEvent(e);
+ }
+ };
+
+ if (self.opt.enablekeyboard) self.bind(document, (cap.isopera && !cap.isopera12) ? "keypress" : "keydown", self.onkeypress);
+
+ self.bind(document, "keydown", function(e) {
+ var ctrl = e.ctrlKey || false;
+ if (ctrl) self.wheelprevented = true;
+ });
+ self.bind(document, "keyup", function(e) {
+ var ctrl = e.ctrlKey || false;
+ if (!ctrl) self.wheelprevented = false;
+ });
+ self.bind(window,"blur",function(e){
+ self.wheelprevented = false;
+ });
+
+ self.bind(window, 'resize', self.lazyResize);
+ self.bind(window, 'orientationchange', self.lazyResize);
+
+ self.bind(window, "load", self.lazyResize);
+
+ if (cap.ischrome && !self.ispage && !self.haswrapper) { //chrome void scrollbar bug - it persists in version 26
+ var tmp = self.win.attr("style");
+ var ww = parseFloat(self.win.css("width")) + 1;
+ self.win.css('width', ww);
+ self.synched("chromefix", function() {
+ self.win.attr("style", tmp)
+ });
+ }
+
+
+ // Trying a cross-browser implementation - good luck!
+
+ self.onAttributeChange = function(e) {
+ self.lazyResize(self.isieold ? 250 : 30);
+ };
+
+ if (ClsMutationObserver !== false) {
+ self.observerbody = new ClsMutationObserver(function(mutations) {
+ mutations.forEach(function(mut){
+ if (mut.type=="attributes") {
+ return ($("body").hasClass("modal-open")) ? self.hide() : self.show(); // Support for Bootstrap modal
+ }
+ });
+ if (document.body.scrollHeight!=self.page.maxh) return self.lazyResize(30);
+ });
+ self.observerbody.observe(document.body, {
+ childList: true,
+ subtree: true,
+ characterData: false,
+ attributes: true,
+ attributeFilter: ['class']
+ });
+ }
+
+ if (!self.ispage && !self.haswrapper) {
+ // redesigned MutationObserver for Chrome18+/Firefox14+/iOS6+ with support for: remove div, add/remove content
+ if (ClsMutationObserver !== false) {
+ self.observer = new ClsMutationObserver(function(mutations) {
+ mutations.forEach(self.onAttributeChange);
+ });
+ self.observer.observe(self.win[0], {
+ childList: true,
+ characterData: false,
+ attributes: true,
+ subtree: false
+ });
+ self.observerremover = new ClsMutationObserver(function(mutations) {
+ mutations.forEach(function(mo) {
+ if (mo.removedNodes.length > 0) {
+ for (var dd in mo.removedNodes) {
+ if (!!self && (mo.removedNodes[dd] == self.win[0])) return self.remove();
+ }
+ }
+ });
+ });
+ self.observerremover.observe(self.win[0].parentNode, {
+ childList: true,
+ characterData: false,
+ attributes: false,
+ subtree: false
+ });
+ } else {
+ self.bind(self.win, (cap.isie && !cap.isie9) ? "propertychange" : "DOMAttrModified", self.onAttributeChange);
+ if (cap.isie9) self.win[0].attachEvent("onpropertychange", self.onAttributeChange); //IE9 DOMAttrModified bug
+ self.bind(self.win, "DOMNodeRemoved", function(e) {
+ if (e.target == self.win[0]) self.remove();
+ });
+ }
+ }
+
+ //
+
+ if (!self.ispage && self.opt.boxzoom) self.bind(window, "resize", self.resizeZoom);
+ if (self.istextarea) self.bind(self.win, "mouseup", self.lazyResize);
+
+ // self.checkrtlmode = true;
+ self.lazyResize(30);
+
+ }
+
+ if (this.doc[0].nodeName == 'IFRAME') {
+ var oniframeload = function() {
+ self.iframexd = false;
+ var doc;
+ try {
+ doc = 'contentDocument' in this ? this.contentDocument : this.contentWindow.document;
+ var a = doc.domain;
+ } catch (e) {
+ self.iframexd = true;
+ doc = false
+ }
+
+ if (self.iframexd) {
+ if ("console" in window) console.log('NiceScroll error: policy restriced iframe');
+ return true; //cross-domain - I can't manage this
+ }
+
+ self.forcescreen = true;
+
+ if (self.isiframe) {
+ self.iframe = {
+ "doc": $(doc),
+ "html": self.doc.contents().find('html')[0],
+ "body": self.doc.contents().find('body')[0]
+ };
+ self.getContentSize = function() {
+ return {
+ w: Math.max(self.iframe.html.scrollWidth, self.iframe.body.scrollWidth),
+ h: Math.max(self.iframe.html.scrollHeight, self.iframe.body.scrollHeight)
+ };
+ };
+ self.docscroll = $(self.iframe.body); //$(this.contentWindow);
+ }
+
+ if (!cap.isios && self.opt.iframeautoresize && !self.isiframe) {
+ self.win.scrollTop(0); // reset position
+ self.doc.height(""); //reset height to fix browser bug
+ var hh = Math.max(doc.getElementsByTagName('html')[0].scrollHeight, doc.body.scrollHeight);
+ self.doc.height(hh);
+ }
+ self.lazyResize(30);
+
+ if (cap.isie7) self.css($(self.iframe.html), {
+ 'overflow-y': 'hidden'
+ });
+ self.css($(self.iframe.body), {
+ 'overflow-y': 'hidden'
+ });
+
+ if (cap.isios && self.haswrapper) {
+ self.css($(doc.body), {
+ '-webkit-transform': 'translate3d(0,0,0)'
+ }); // avoid iFrame content clipping - thanks to http://blog.derraab.com/2012/04/02/avoid-iframe-content-clipping-with-css-transform-on-ios/
+ }
+
+ if ('contentWindow' in this) {
+ self.bind(this.contentWindow, "scroll", self.onscroll); //IE8 & minor
+ } else {
+ self.bind(doc, "scroll", self.onscroll);
+ }
+
+ if (self.opt.enablemousewheel) {
+ self.bind(doc, "mousewheel", self.onmousewheel);
+ }
+
+ if (self.opt.enablekeyboard) self.bind(doc, (cap.isopera) ? "keypress" : "keydown", self.onkeypress);
+
+ if (cap.cantouch || self.opt.touchbehavior) {
+ self.bind(doc, "mousedown", self.ontouchstart);
+ self.bind(doc, "mousemove", function(e) {
+ return self.ontouchmove(e, true)
+ });
+ if (self.opt.grabcursorenabled && cap.cursorgrabvalue) self.css($(doc.body), {
+ 'cursor': cap.cursorgrabvalue
+ });
+ }
+
+ self.bind(doc, "mouseup", self.ontouchend);
+
+ if (self.zoom) {
+ if (self.opt.dblclickzoom) self.bind(doc, 'dblclick', self.doZoom);
+ if (self.ongesturezoom) self.bind(doc, "gestureend", self.ongesturezoom);
+ }
+ };
+
+ if (this.doc[0].readyState && this.doc[0].readyState == "complete") {
+ setTimeout(function() {
+ oniframeload.call(self.doc[0], false)
+ }, 500);
+ }
+ self.bind(this.doc, "load", oniframeload);
+
+ }
+
+ };
+
+ this.showCursor = function(py, px) {
+ if (self.cursortimeout) {
+ clearTimeout(self.cursortimeout);
+ self.cursortimeout = 0;
+ }
+ if (!self.rail) return;
+ if (self.autohidedom) {
+ self.autohidedom.stop().css({
+ opacity: self.opt.cursoropacitymax
+ });
+ self.cursoractive = true;
+ }
+
+ if (!self.rail.drag || self.rail.drag.pt != 1) {
+ if ((typeof py != "undefined") && (py !== false)) {
+ self.scroll.y = Math.round(py * 1 / self.scrollratio.y);
+ }
+ if (typeof px != "undefined") {
+ self.scroll.x = Math.round(px * 1 / self.scrollratio.x);
+ }
+ }
+
+ self.cursor.css({
+ height: self.cursorheight,
+ top: self.scroll.y
+ });
+ if (self.cursorh) {
+ var lx = (self.hasreversehr) ? self.scrollvaluemaxw-self.scroll.x : self.scroll.x;
+ (!self.rail.align && self.rail.visibility) ? self.cursorh.css({
+ width: self.cursorwidth,
+ left: lx + self.rail.width
+ }): self.cursorh.css({
+ width: self.cursorwidth,
+ left: lx
+ });
+ self.cursoractive = true;
+ }
+
+ if (self.zoom) self.zoom.stop().css({
+ opacity: self.opt.cursoropacitymax
+ });
+ };
+
+ this.hideCursor = function(tm) {
+ if (self.cursortimeout) return;
+ if (!self.rail) return;
+ if (!self.autohidedom) return;
+ if (self.hasmousefocus && self.opt.autohidemode == "leave") return;
+ self.cursortimeout = setTimeout(function() {
+ if (!self.rail.active || !self.showonmouseevent) {
+ self.autohidedom.stop().animate({
+ opacity: self.opt.cursoropacitymin
+ });
+ if (self.zoom) self.zoom.stop().animate({
+ opacity: self.opt.cursoropacitymin
+ });
+ self.cursoractive = false;
+ }
+ self.cursortimeout = 0;
+ }, tm || self.opt.hidecursordelay);
+ };
+
+ this.noticeCursor = function(tm, py, px) {
+ self.showCursor(py, px);
+ if (!self.rail.active) self.hideCursor(tm);
+ };
+
+ this.getContentSize =
+ (self.ispage) ?
+ function() {
+ return {
+ w: Math.max(document.body.scrollWidth, document.documentElement.scrollWidth),
+ h: Math.max(document.body.scrollHeight, document.documentElement.scrollHeight)
+ }
+ } : (self.haswrapper) ?
+ function() {
+ return {
+ w: self.doc.outerWidth() + parseInt(self.win.css('paddingLeft')) + parseInt(self.win.css('paddingRight')),
+ h: self.doc.outerHeight() + parseInt(self.win.css('paddingTop')) + parseInt(self.win.css('paddingBottom'))
+ }
+ } : function() {
+ return {
+ w: self.docscroll[0].scrollWidth,
+ h: self.docscroll[0].scrollHeight
+ }
+ };
+
+ this.onResize = function(e, page) {
+
+ if (!self || !self.win) return false;
+
+ if (!self.haswrapper && !self.ispage) {
+ if (self.win.css('display') == 'none') {
+ if (self.visibility) self.hideRail().hideRailHr();
+ return false;
+ } else {
+ if (!self.hidden && !self.visibility) self.showRail().showRailHr();
+ }
+ }
+
+ var premaxh = self.page.maxh;
+ var premaxw = self.page.maxw;
+
+ var preview = {
+ h: self.view.h,
+ w: self.view.w
+ };
+
+ self.view = {
+ w: (self.ispage) ? self.win.width() : parseInt(self.win[0].clientWidth),
+ h: (self.ispage) ? self.win.height() : parseInt(self.win[0].clientHeight)
+ };
+
+ self.page = (page) ? page : self.getContentSize();
+
+ self.page.maxh = Math.max(0, self.page.h - self.view.h);
+ self.page.maxw = Math.max(0, self.page.w - self.view.w);
+
+ if ((self.page.maxh == premaxh) && (self.page.maxw == premaxw) && (self.view.w == preview.w) && (self.view.h == preview.h)) {
+ // test position
+ if (!self.ispage) {
+ var pos = self.win.offset();
+ if (self.lastposition) {
+ var lst = self.lastposition;
+ if ((lst.top == pos.top) && (lst.left == pos.left)) return self; //nothing to do
+ }
+ self.lastposition = pos;
+ } else {
+ return self; //nothing to do
+ }
+ }
+
+ if (self.page.maxh == 0) {
+ self.hideRail();
+ self.scrollvaluemax = 0;
+ self.scroll.y = 0;
+ self.scrollratio.y = 0;
+ self.cursorheight = 0;
+ self.setScrollTop(0);
+ self.rail.scrollable = false;
+ } else {
+ self.page.maxh -= (self.opt.railpadding.top + self.opt.railpadding.bottom); //**
+ self.rail.scrollable = true;
+ }
+
+ if (self.page.maxw == 0) {
+ self.hideRailHr();
+ self.scrollvaluemaxw = 0;
+ self.scroll.x = 0;
+ self.scrollratio.x = 0;
+ self.cursorwidth = 0;
+ self.setScrollLeft(0);
+ self.railh.scrollable = false;
+ } else {
+ self.page.maxw -= (self.opt.railpadding.left + self.opt.railpadding.right); //**
+ self.railh.scrollable = true;
+ }
+
+ self.railslocked = (self.locked) || ((self.page.maxh == 0) && (self.page.maxw == 0));
+ if (self.railslocked) {
+ if (!self.ispage) self.updateScrollBar(self.view);
+ return false;
+ }
+
+ if (!self.hidden && !self.visibility) {
+ self.showRail().showRailHr();
+ }
+ else if (!self.hidden && !self.railh.visibility) self.showRailHr();
+
+ if (self.istextarea && self.win.css('resize') && self.win.css('resize') != 'none') self.view.h -= 20;
+
+ self.cursorheight = Math.min(self.view.h, Math.round(self.view.h * (self.view.h / self.page.h)));
+ self.cursorheight = (self.opt.cursorfixedheight) ? self.opt.cursorfixedheight : Math.max(self.opt.cursorminheight, self.cursorheight);
+
+ self.cursorwidth = Math.min(self.view.w, Math.round(self.view.w * (self.view.w / self.page.w)));
+ self.cursorwidth = (self.opt.cursorfixedheight) ? self.opt.cursorfixedheight : Math.max(self.opt.cursorminheight, self.cursorwidth);
+
+ self.scrollvaluemax = self.view.h - self.cursorheight - self.cursor.hborder - (self.opt.railpadding.top + self.opt.railpadding.bottom); //**
+
+ if (self.railh) {
+ self.railh.width = (self.page.maxh > 0) ? (self.view.w - self.rail.width) : self.view.w;
+ self.scrollvaluemaxw = self.railh.width - self.cursorwidth - self.cursorh.wborder - (self.opt.railpadding.left + self.opt.railpadding.right); //**
+ }
+
+ /*
+ if (self.checkrtlmode&&self.railh) {
+ self.checkrtlmode = false;
+ if (self.opt.rtlmode&&self.scroll.x==0) self.setScrollLeft(self.page.maxw);
+ }
+*/
+
+ if (!self.ispage) self.updateScrollBar(self.view);
+
+ self.scrollratio = {
+ x: (self.page.maxw / self.scrollvaluemaxw),
+ y: (self.page.maxh / self.scrollvaluemax)
+ };
+
+ var sy = self.getScrollTop();
+ if (sy > self.page.maxh) {
+ self.doScrollTop(self.page.maxh);
+ } else {
+ self.scroll.y = Math.round(self.getScrollTop() * (1 / self.scrollratio.y));
+ self.scroll.x = Math.round(self.getScrollLeft() * (1 / self.scrollratio.x));
+ if (self.cursoractive) self.noticeCursor();
+ }
+
+ if (self.scroll.y && (self.getScrollTop() == 0)) self.doScrollTo(Math.floor(self.scroll.y * self.scrollratio.y));
+
+ return self;
+ };
+
+ this.resize = self.onResize;
+
+ this.lazyResize = function(tm) { // event debounce
+ tm = (isNaN(tm)) ? 30 : tm;
+ self.debounced('resize', self.resize, tm);
+ return self;
+ };
+
+ // modified by MDN https://developer.mozilla.org/en-US/docs/DOM/Mozilla_event_reference/wheel
+ function _modernWheelEvent(dom, name, fn, bubble) {
+ self._bind(dom, name, function(e) {
+ var e = (e) ? e : window.event;
+ var event = {
+ original: e,
+ target: e.target || e.srcElement,
+ type: "wheel",
+ deltaMode: e.type == "MozMousePixelScroll" ? 0 : 1,
+ deltaX: 0,
+ deltaZ: 0,
+ preventDefault: function() {
+ e.preventDefault ? e.preventDefault() : e.returnValue = false;
+ return false;
+ },
+ stopImmediatePropagation: function() {
+ (e.stopImmediatePropagation) ? e.stopImmediatePropagation(): e.cancelBubble = true;
+ }
+ };
+
+ if (name == "mousewheel") {
+ event.deltaY = -1 / 40 * e.wheelDelta;
+ e.wheelDeltaX && (event.deltaX = -1 / 40 * e.wheelDeltaX);
+ } else {
+ event.deltaY = e.detail;
+ }
+
+ return fn.call(dom, event);
+ }, bubble);
+ };
+
+
+
+ this.jqbind = function(dom, name, fn) { // use jquery bind for non-native events (mouseenter/mouseleave)
+ self.events.push({
+ e: dom,
+ n: name,
+ f: fn,
+ q: true
+ });
+ $(dom).bind(name, fn);
+ };
+
+ this.bind = function(dom, name, fn, bubble) { // touch-oriented & fixing jquery bind
+ var el = ("jquery" in dom) ? dom[0] : dom;
+
+ if (name == 'mousewheel') {
+ if (window.addEventListener||'onwheel' in document) { // modern brosers & IE9 detection fix
+ self._bind(el, "wheel", fn, bubble || false);
+ } else {
+ var wname = (typeof document.onmousewheel != "undefined") ? "mousewheel" : "DOMMouseScroll"; // older IE/Firefox
+ _modernWheelEvent(el, wname, fn, bubble || false);
+ if (wname == "DOMMouseScroll") _modernWheelEvent(el, "MozMousePixelScroll", fn, bubble || false); // Firefox legacy
+ }
+ } else if (el.addEventListener) {
+ if (cap.cantouch && /mouseup|mousedown|mousemove/.test(name)) { // touch device support
+ var tt = (name == 'mousedown') ? 'touchstart' : (name == 'mouseup') ? 'touchend' : 'touchmove';
+ self._bind(el, tt, function(e) {
+ if (e.touches) {
+ if (e.touches.length < 2) {
+ var ev = (e.touches.length) ? e.touches[0] : e;
+ ev.original = e;
+ fn.call(this, ev);
+ }
+ } else if (e.changedTouches) {
+ var ev = e.changedTouches[0];
+ ev.original = e;
+ fn.call(this, ev);
+ } //blackberry
+ }, bubble || false);
+ }
+ self._bind(el, name, fn, bubble || false);
+ if (cap.cantouch && name == "mouseup") self._bind(el, "touchcancel", fn, bubble || false);
+ } else {
+ self._bind(el, name, function(e) {
+ e = e || window.event || false;
+ if (e) {
+ if (e.srcElement) e.target = e.srcElement;
+ }
+ if (!("pageY" in e)) {
+ e.pageX = e.clientX + document.documentElement.scrollLeft;
+ e.pageY = e.clientY + document.documentElement.scrollTop;
+ }
+ return ((fn.call(el, e) === false) || bubble === false) ? self.cancelEvent(e) : true;
+ });
+ }
+ };
+
+ if (cap.haseventlistener) { // W3C standard model
+ this._bind = function(el, name, fn, bubble) { // primitive bind
+ self.events.push({
+ e: el,
+ n: name,
+ f: fn,
+ b: bubble,
+ q: false
+ });
+ el.addEventListener(name, fn, bubble || false);
+ };
+ this.cancelEvent = function(e) {
+ if (!e) return false;
+ var e = (e.original) ? e.original : e;
+ e.preventDefault();
+ e.stopPropagation();
+ if (e.preventManipulation) e.preventManipulation(); //IE10
+ return false;
+ };
+ this.stopPropagation = function(e) {
+ if (!e) return false;
+ var e = (e.original) ? e.original : e;
+ e.stopPropagation();
+ return false;
+ };
+ this._unbind = function(el, name, fn, bub) { // primitive unbind
+ el.removeEventListener(name, fn, bub);
+ };
+ } else { // old IE model
+ this._bind = function(el, name, fn, bubble) { // primitive bind
+ self.events.push({
+ e: el,
+ n: name,
+ f: fn,
+ b: bubble,
+ q: false
+ });
+ if (el.attachEvent) {
+ el.attachEvent("on" + name, fn);
+ } else {
+ el["on" + name] = fn;
+ }
+ };
+ // Thanks to http://www.switchonthecode.com !!
+ this.cancelEvent = function(e) {
+ var e = window.event || false;
+ if (!e) return false;
+ e.cancelBubble = true;
+ e.cancel = true;
+ e.returnValue = false;
+ return false;
+ };
+ this.stopPropagation = function(e) {
+ var e = window.event || false;
+ if (!e) return false;
+ e.cancelBubble = true;
+ return false;
+ };
+ this._unbind = function(el, name, fn, bub) { // primitive unbind IE old
+ if (el.detachEvent) {
+ el.detachEvent('on' + name, fn);
+ } else {
+ el['on' + name] = false;
+ }
+ };
+ }
+
+ this.unbindAll = function() {
+ for (var a = 0; a < self.events.length; a++) {
+ var r = self.events[a];
+ (r.q) ? r.e.unbind(r.n, r.f): self._unbind(r.e, r.n, r.f, r.b);
+ }
+ };
+
+ this.showRail = function() {
+ if ((self.page.maxh != 0) && (self.ispage || self.win.css('display') != 'none')) {
+ self.visibility = true;
+ self.rail.visibility = true;
+ self.rail.css('display', 'block');
+ }
+ return self;
+ };
+
+ this.showRailHr = function() {
+ if (!self.railh) return self;
+ if ((self.page.maxw != 0) && (self.ispage || self.win.css('display') != 'none')) {
+ self.railh.visibility = true;
+ self.railh.css('display', 'block');
+ }
+ return self;
+ };
+
+ this.hideRail = function() {
+ self.visibility = false;
+ self.rail.visibility = false;
+ self.rail.css('display', 'none');
+ return self;
+ };
+
+ this.hideRailHr = function() {
+ if (!self.railh) return self;
+ self.railh.visibility = false;
+ self.railh.css('display', 'none');
+ return self;
+ };
+
+ this.show = function() {
+ self.hidden = false;
+ self.railslocked = false;
+ return self.showRail().showRailHr();
+ };
+
+ this.hide = function() {
+ self.hidden = true;
+ self.railslocked = true;
+ return self.hideRail().hideRailHr();
+ };
+
+ this.toggle = function() {
+ return (self.hidden) ? self.show() : self.hide();
+ };
+
+ this.remove = function() {
+ self.stop();
+ if (self.cursortimeout) clearTimeout(self.cursortimeout);
+ self.doZoomOut();
+ self.unbindAll();
+
+ if (cap.isie9) self.win[0].detachEvent("onpropertychange", self.onAttributeChange); //IE9 DOMAttrModified bug
+
+ if (self.observer !== false) self.observer.disconnect();
+ if (self.observerremover !== false) self.observerremover.disconnect();
+ if (self.observerbody !== false) self.observerbody.disconnect();
+
+ self.events = null;
+
+ if (self.cursor) {
+ self.cursor.remove();
+ }
+ if (self.cursorh) {
+ self.cursorh.remove();
+ }
+ if (self.rail) {
+ self.rail.remove();
+ }
+ if (self.railh) {
+ self.railh.remove();
+ }
+ if (self.zoom) {
+ self.zoom.remove();
+ }
+ for (var a = 0; a < self.saved.css.length; a++) {
+ var d = self.saved.css[a];
+ d[0].css(d[1], (typeof d[2] == "undefined") ? '' : d[2]);
+ }
+ self.saved = false;
+ self.me.data('__nicescroll', ''); //erase all traces
+
+ // memory leak fixed by GianlucaGuarini - thanks a lot!
+ // remove the current nicescroll from the $.nicescroll array & normalize array
+ var lst = $.nicescroll;
+ lst.each(function(i) {
+ if (!this) return;
+ if (this.id === self.id) {
+ delete lst[i];
+ for (var b = ++i; b < lst.length; b++, i++) lst[i] = lst[b];
+ lst.length--;
+ if (lst.length) delete lst[lst.length];
+ }
+ });
+
+ for (var i in self) {
+ self[i] = null;
+ delete self[i];
+ }
+
+ self = null;
+
+ };
+
+ this.scrollstart = function(fn) {
+ this.onscrollstart = fn;
+ return self;
+ };
+ this.scrollend = function(fn) {
+ this.onscrollend = fn;
+ return self;
+ };
+ this.scrollcancel = function(fn) {
+ this.onscrollcancel = fn;
+ return self;
+ };
+
+ this.zoomin = function(fn) {
+ this.onzoomin = fn;
+ return self;
+ };
+ this.zoomout = function(fn) {
+ this.onzoomout = fn;
+ return self;
+ };
+
+ this.isScrollable = function(e) {
+ var dom = (e.target) ? e.target : e;
+ if (dom.nodeName == 'OPTION') return true;
+ while (dom && (dom.nodeType == 1) && !(/^BODY|HTML/.test(dom.nodeName))) {
+ var dd = $(dom);
+ var ov = dd.css('overflowY') || dd.css('overflowX') || dd.css('overflow') || '';
+ if (/scroll|auto/.test(ov)) return (dom.clientHeight != dom.scrollHeight);
+ dom = (dom.parentNode) ? dom.parentNode : false;
+ }
+ return false;
+ };
+
+ this.getViewport = function(me) {
+ var dom = (me && me.parentNode) ? me.parentNode : false;
+ while (dom && (dom.nodeType == 1) && !(/^BODY|HTML/.test(dom.nodeName))) {
+ var dd = $(dom);
+ if (/fixed|absolute/.test(dd.css("position"))) return dd;
+ var ov = dd.css('overflowY') || dd.css('overflowX') || dd.css('overflow') || '';
+ if ((/scroll|auto/.test(ov)) && (dom.clientHeight != dom.scrollHeight)) return dd;
+ if (dd.getNiceScroll().length > 0) return dd;
+ dom = (dom.parentNode) ? dom.parentNode : false;
+ }
+ return false; //(dom) ? $(dom) : false;
+ };
+
+ this.triggerScrollEnd = function() {
+ if (!self.onscrollend) return;
+
+ var px = self.getScrollLeft();
+ var py = self.getScrollTop();
+
+ var info = {
+ "type": "scrollend",
+ "current": {
+ "x": px,
+ "y": py
+ },
+ "end": {
+ "x": px,
+ "y": py
+ }
+ };
+ self.onscrollend.call(self, info);
+ }
+
+ function execScrollWheel(e, hr, chkscroll) {
+ var px, py;
+
+ if (e.deltaMode == 0) { // PIXEL
+ px = -Math.floor(e.deltaX * (self.opt.mousescrollstep / (18 * 3)));
+ py = -Math.floor(e.deltaY * (self.opt.mousescrollstep / (18 * 3)));
+ } else if (e.deltaMode == 1) { // LINE
+ px = -Math.floor(e.deltaX * self.opt.mousescrollstep);
+ py = -Math.floor(e.deltaY * self.opt.mousescrollstep);
+ }
+
+ if (hr && self.opt.oneaxismousemode && (px == 0) && py) { // classic vertical-only mousewheel + browser with x/y support
+ px = py;
+ py = 0;
+
+ if (chkscroll) {
+ var hrend = (px < 0) ? (self.getScrollLeft() >= self.page.maxw) : (self.getScrollLeft() <= 0);
+ if (hrend) { // preserve vertical scrolling
+ py = px;
+ px = 0;
+ }
+ }
+
+ }
+
+ if (px) {
+ if (self.scrollmom) {
+ self.scrollmom.stop()
+ }
+ self.lastdeltax += px;
+ self.debounced("mousewheelx", function() {
+ var dt = self.lastdeltax;
+ self.lastdeltax = 0;
+ if (!self.rail.drag) {
+ self.doScrollLeftBy(dt)
+ }
+ }, 15);
+ }
+ if (py) {
+ if (self.opt.nativeparentscrolling && chkscroll && !self.ispage && !self.zoomactive) {
+ if (py < 0) {
+ if (self.getScrollTop() >= self.page.maxh) return true;
+ } else {
+ if (self.getScrollTop() <= 0) return true;
+ }
+ }
+ if (self.scrollmom) {
+ self.scrollmom.stop()
+ }
+ self.lastdeltay += py;
+ self.debounced("mousewheely", function() {
+ var dt = self.lastdeltay;
+ self.lastdeltay = 0;
+ if (!self.rail.drag) {
+ self.doScrollBy(dt)
+ }
+ }, 15);
+ }
+
+ e.stopImmediatePropagation();
+ return e.preventDefault();
+ };
+
+ this.onmousewheel = function(e) {
+ if (self.wheelprevented) return;
+ if (self.railslocked) {
+ self.debounced("checkunlock", self.resize, 250);
+ return true;
+ }
+ if (self.rail.drag) return self.cancelEvent(e);
+
+ if (self.opt.oneaxismousemode == "auto" && e.deltaX != 0) self.opt.oneaxismousemode = false; // check two-axis mouse support (not very elegant)
+
+ if (self.opt.oneaxismousemode && e.deltaX == 0) {
+ if (!self.rail.scrollable) {
+ if (self.railh && self.railh.scrollable) {
+ return self.onmousewheelhr(e);
+ } else {
+ return true;
+ }
+ }
+ }
+
+ var nw = +(new Date());
+ var chk = false;
+ if (self.opt.preservenativescrolling && ((self.checkarea + 600) < nw)) {
+ self.nativescrollingarea = self.isScrollable(e);
+ chk = true;
+ }
+ self.checkarea = nw;
+ if (self.nativescrollingarea) return true; // this isn't my business
+ var ret = execScrollWheel(e, false, chk);
+ if (ret) self.checkarea = 0;
+ return ret;
+ };
+
+ this.onmousewheelhr = function(e) {
+ if (self.wheelprevented) return;
+ if (self.railslocked || !self.railh.scrollable) return true;
+ if (self.rail.drag) return self.cancelEvent(e);
+
+ var nw = +(new Date());
+ var chk = false;
+ if (self.opt.preservenativescrolling && ((self.checkarea + 600) < nw)) {
+ self.nativescrollingarea = self.isScrollable(e);
+ chk = true;
+ }
+ self.checkarea = nw;
+ if (self.nativescrollingarea) return true; // this isn't my business
+ if (self.railslocked) return self.cancelEvent(e);
+
+ return execScrollWheel(e, true, chk);
+ };
+
+ this.stop = function() {
+ self.cancelScroll();
+ if (self.scrollmon) self.scrollmon.stop();
+ self.cursorfreezed = false;
+ self.scroll.y = Math.round(self.getScrollTop() * (1 / self.scrollratio.y));
+ self.noticeCursor();
+ return self;
+ };
+
+ this.getTransitionSpeed = function(dif) {
+ var sp = Math.round(self.opt.scrollspeed * 10);
+ var ex = Math.min(sp, Math.round((dif / 20) * self.opt.scrollspeed));
+ return (ex > 20) ? ex : 0;
+ };
+
+ if (!self.opt.smoothscroll) {
+ this.doScrollLeft = function(x, spd) { //direct
+ var y = self.getScrollTop();
+ self.doScrollPos(x, y, spd);
+ };
+ this.doScrollTop = function(y, spd) { //direct
+ var x = self.getScrollLeft();
+ self.doScrollPos(x, y, spd);
+ };
+ this.doScrollPos = function(x, y, spd) { //direct
+ var nx = (x > self.page.maxw) ? self.page.maxw : x;
+ if (nx < 0) nx = 0;
+ var ny = (y > self.page.maxh) ? self.page.maxh : y;
+ if (ny < 0) ny = 0;
+ self.synched('scroll', function() {
+ self.setScrollTop(ny);
+ self.setScrollLeft(nx);
+ });
+ };
+ this.cancelScroll = function() {}; // direct
+ } else if (self.ishwscroll && cap.hastransition && self.opt.usetransition && !!self.opt.smoothscroll) {
+ this.prepareTransition = function(dif, istime) {
+ var ex = (istime) ? ((dif > 20) ? dif : 0) : self.getTransitionSpeed(dif);
+ var trans = (ex) ? cap.prefixstyle + 'transform ' + ex + 'ms ease-out' : '';
+ if (!self.lasttransitionstyle || self.lasttransitionstyle != trans) {
+ self.lasttransitionstyle = trans;
+ self.doc.css(cap.transitionstyle, trans);
+ }
+ return ex;
+ };
+
+ this.doScrollLeft = function(x, spd) { //trans
+ var y = (self.scrollrunning) ? self.newscrolly : self.getScrollTop();
+ self.doScrollPos(x, y, spd);
+ };
+
+ this.doScrollTop = function(y, spd) { //trans
+ var x = (self.scrollrunning) ? self.newscrollx : self.getScrollLeft();
+ self.doScrollPos(x, y, spd);
+ };
+
+ this.doScrollPos = function(x, y, spd) { //trans
+
+ var py = self.getScrollTop();
+ var px = self.getScrollLeft();
+
+ if (((self.newscrolly - py) * (y - py) < 0) || ((self.newscrollx - px) * (x - px) < 0)) self.cancelScroll(); //inverted movement detection
+
+ if (self.opt.bouncescroll == false) {
+ if (y < 0) y = 0;
+ else if (y > self.page.maxh) y = self.page.maxh;
+ if (x < 0) x = 0;
+ else if (x > self.page.maxw) x = self.page.maxw;
+ }
+
+ if (self.scrollrunning && x == self.newscrollx && y == self.newscrolly) return false;
+
+ self.newscrolly = y;
+ self.newscrollx = x;
+
+ self.newscrollspeed = spd || false;
+
+ if (self.timer) return false;
+
+ self.timer = setTimeout(function() {
+
+ var top = self.getScrollTop();
+ var lft = self.getScrollLeft();
+
+ var dst = {};
+ dst.x = x - lft;
+ dst.y = y - top;
+ dst.px = lft;
+ dst.py = top;
+
+ var dd = Math.round(Math.sqrt(Math.pow(dst.x, 2) + Math.pow(dst.y, 2)));
+ var ms = (self.newscrollspeed && self.newscrollspeed > 1) ? self.newscrollspeed : self.getTransitionSpeed(dd);
+ if (self.newscrollspeed && self.newscrollspeed <= 1) ms *= self.newscrollspeed;
+
+ self.prepareTransition(ms, true);
+
+ if (self.timerscroll && self.timerscroll.tm) clearInterval(self.timerscroll.tm);
+
+ if (ms > 0) {
+
+ if (!self.scrollrunning && self.onscrollstart) {
+ var info = {
+ "type": "scrollstart",
+ "current": {
+ "x": lft,
+ "y": top
+ },
+ "request": {
+ "x": x,
+ "y": y
+ },
+ "end": {
+ "x": self.newscrollx,
+ "y": self.newscrolly
+ },
+ "speed": ms
+ };
+ self.onscrollstart.call(self, info);
+ }
+
+ if (cap.transitionend) {
+ if (!self.scrollendtrapped) {
+ self.scrollendtrapped = true;
+ self.bind(self.doc, cap.transitionend, self.onScrollTransitionEnd, false); //I have got to do something usefull!!
+ }
+ } else {
+ if (self.scrollendtrapped) clearTimeout(self.scrollendtrapped);
+ self.scrollendtrapped = setTimeout(self.onScrollTransitionEnd, ms); // simulate transitionend event
+ }
+
+ var py = top;
+ var px = lft;
+ self.timerscroll = {
+ bz: new BezierClass(py, self.newscrolly, ms, 0, 0, 0.58, 1),
+ bh: new BezierClass(px, self.newscrollx, ms, 0, 0, 0.58, 1)
+ };
+ if (!self.cursorfreezed) self.timerscroll.tm = setInterval(function() {
+ self.showCursor(self.getScrollTop(), self.getScrollLeft())
+ }, 60);
+
+ }
+
+ self.synched("doScroll-set", function() {
+ self.timer = 0;
+ if (self.scrollendtrapped) self.scrollrunning = true;
+ self.setScrollTop(self.newscrolly);
+ self.setScrollLeft(self.newscrollx);
+ if (!self.scrollendtrapped) self.onScrollTransitionEnd();
+ });
+
+
+ }, 50);
+
+ };
+
+ this.cancelScroll = function() {
+ if (!self.scrollendtrapped) return true;
+ var py = self.getScrollTop();
+ var px = self.getScrollLeft();
+ self.scrollrunning = false;
+ if (!cap.transitionend) clearTimeout(cap.transitionend);
+ self.scrollendtrapped = false;
+ self._unbind(self.doc[0], cap.transitionend, self.onScrollTransitionEnd);
+ self.prepareTransition(0);
+ self.setScrollTop(py); // fire event onscroll
+ if (self.railh) self.setScrollLeft(px);
+ if (self.timerscroll && self.timerscroll.tm) clearInterval(self.timerscroll.tm);
+ self.timerscroll = false;
+
+ self.cursorfreezed = false;
+
+ self.showCursor(py, px);
+ return self;
+ };
+ this.onScrollTransitionEnd = function() {
+ if (self.scrollendtrapped) self._unbind(self.doc[0], cap.transitionend, self.onScrollTransitionEnd);
+ self.scrollendtrapped = false;
+ self.prepareTransition(0);
+ if (self.timerscroll && self.timerscroll.tm) clearInterval(self.timerscroll.tm);
+ self.timerscroll = false;
+ var py = self.getScrollTop();
+ var px = self.getScrollLeft();
+ self.setScrollTop(py); // fire event onscroll
+ if (self.railh) self.setScrollLeft(px); // fire event onscroll left
+
+ self.noticeCursor(false, py, px);
+
+ self.cursorfreezed = false;
+
+ if (py < 0) py = 0
+ else if (py > self.page.maxh) py = self.page.maxh;
+ if (px < 0) px = 0
+ else if (px > self.page.maxw) px = self.page.maxw;
+ if ((py != self.newscrolly) || (px != self.newscrollx)) return self.doScrollPos(px, py, self.opt.snapbackspeed);
+
+ if (self.onscrollend && self.scrollrunning) {
+ self.triggerScrollEnd();
+ }
+ self.scrollrunning = false;
+
+ };
+
+ } else {
+
+ this.doScrollLeft = function(x, spd) { //no-trans
+ var y = (self.scrollrunning) ? self.newscrolly : self.getScrollTop();
+ self.doScrollPos(x, y, spd);
+ };
+
+ this.doScrollTop = function(y, spd) { //no-trans
+ var x = (self.scrollrunning) ? self.newscrollx : self.getScrollLeft();
+ self.doScrollPos(x, y, spd);
+ };
+
+ this.doScrollPos = function(x, y, spd) { //no-trans
+ var y = ((typeof y == "undefined") || (y === false)) ? self.getScrollTop(true) : y;
+
+ if ((self.timer) && (self.newscrolly == y) && (self.newscrollx == x)) return true;
+
+ if (self.timer) clearAnimationFrame(self.timer);
+ self.timer = 0;
+
+ var py = self.getScrollTop();
+ var px = self.getScrollLeft();
+
+ if (((self.newscrolly - py) * (y - py) < 0) || ((self.newscrollx - px) * (x - px) < 0)) self.cancelScroll(); //inverted movement detection
+
+ self.newscrolly = y;
+ self.newscrollx = x;
+
+ if (!self.bouncescroll || !self.rail.visibility) {
+ if (self.newscrolly < 0) {
+ self.newscrolly = 0;
+ } else if (self.newscrolly > self.page.maxh) {
+ self.newscrolly = self.page.maxh;
+ }
+ }
+ if (!self.bouncescroll || !self.railh.visibility) {
+ if (self.newscrollx < 0) {
+ self.newscrollx = 0;
+ } else if (self.newscrollx > self.page.maxw) {
+ self.newscrollx = self.page.maxw;
+ }
+ }
+
+ self.dst = {};
+ self.dst.x = x - px;
+ self.dst.y = y - py;
+ self.dst.px = px;
+ self.dst.py = py;
+
+ var dst = Math.round(Math.sqrt(Math.pow(self.dst.x, 2) + Math.pow(self.dst.y, 2)));
+
+ self.dst.ax = self.dst.x / dst;
+ self.dst.ay = self.dst.y / dst;
+
+ var pa = 0;
+ var pe = dst;
+
+ if (self.dst.x == 0) {
+ pa = py;
+ pe = y;
+ self.dst.ay = 1;
+ self.dst.py = 0;
+ } else if (self.dst.y == 0) {
+ pa = px;
+ pe = x;
+ self.dst.ax = 1;
+ self.dst.px = 0;
+ }
+
+ var ms = self.getTransitionSpeed(dst);
+ if (spd && spd <= 1) ms *= spd;
+ if (ms > 0) {
+ self.bzscroll = (self.bzscroll) ? self.bzscroll.update(pe, ms) : new BezierClass(pa, pe, ms, 0, 1, 0, 1);
+ } else {
+ self.bzscroll = false;
+ }
+
+ if (self.timer) return;
+
+ if ((py == self.page.maxh && y >= self.page.maxh) || (px == self.page.maxw && x >= self.page.maxw)) self.checkContentSize();
+
+ var sync = 1;
+
+ function scrolling() {
+ if (self.cancelAnimationFrame) return true;
+
+ self.scrollrunning = true;
+
+ sync = 1 - sync;
+ if (sync) return (self.timer = setAnimationFrame(scrolling) || 1);
+
+ var done = 0;
+ var sx, sy;
+
+ var sc = sy = self.getScrollTop();
+ if (self.dst.ay) {
+ sc = (self.bzscroll) ? self.dst.py + (self.bzscroll.getNow() * self.dst.ay) : self.newscrolly;
+ var dr = sc - sy;
+ if ((dr < 0 && sc < self.newscrolly) || (dr > 0 && sc > self.newscrolly)) sc = self.newscrolly;
+ self.setScrollTop(sc);
+ if (sc == self.newscrolly) done = 1;
+ } else {
+ done = 1;
+ }
+
+ var scx = sx = self.getScrollLeft();
+ if (self.dst.ax) {
+ scx = (self.bzscroll) ? self.dst.px + (self.bzscroll.getNow() * self.dst.ax) : self.newscrollx;
+ var dr = scx - sx;
+ if ((dr < 0 && scx < self.newscrollx) || (dr > 0 && scx > self.newscrollx)) scx = self.newscrollx;
+ self.setScrollLeft(scx);
+ if (scx == self.newscrollx) done += 1;
+ } else {
+ done += 1;
+ }
+
+ if (done == 2) {
+ self.timer = 0;
+ self.cursorfreezed = false;
+ self.bzscroll = false;
+ self.scrollrunning = false;
+ if (sc < 0) sc = 0;
+ else if (sc > self.page.maxh) sc = self.page.maxh;
+ if (scx < 0) scx = 0;
+ else if (scx > self.page.maxw) scx = self.page.maxw;
+ if ((scx != self.newscrollx) || (sc != self.newscrolly)) self.doScrollPos(scx, sc);
+ else {
+ if (self.onscrollend) {
+ self.triggerScrollEnd();
+ }
+ }
+ } else {
+ self.timer = setAnimationFrame(scrolling) || 1;
+ }
+ };
+ self.cancelAnimationFrame = false;
+ self.timer = 1;
+
+ if (self.onscrollstart && !self.scrollrunning) {
+ var info = {
+ "type": "scrollstart",
+ "current": {
+ "x": px,
+ "y": py
+ },
+ "request": {
+ "x": x,
+ "y": y
+ },
+ "end": {
+ "x": self.newscrollx,
+ "y": self.newscrolly
+ },
+ "speed": ms
+ };
+ self.onscrollstart.call(self, info);
+ }
+
+ scrolling();
+
+ if ((py == self.page.maxh && y >= py) || (px == self.page.maxw && x >= px)) self.checkContentSize();
+
+ self.noticeCursor();
+ };
+
+ this.cancelScroll = function() {
+ if (self.timer) clearAnimationFrame(self.timer);
+ self.timer = 0;
+ self.bzscroll = false;
+ self.scrollrunning = false;
+ return self;
+ };
+
+ }
+
+ this.doScrollBy = function(stp, relative) {
+ var ny = 0;
+ if (relative) {
+ ny = Math.floor((self.scroll.y - stp) * self.scrollratio.y)
+ } else {
+ var sy = (self.timer) ? self.newscrolly : self.getScrollTop(true);
+ ny = sy - stp;
+ }
+ if (self.bouncescroll) {
+ var haf = Math.round(self.view.h / 2);
+ if (ny < -haf) ny = -haf
+ else if (ny > (self.page.maxh + haf)) ny = (self.page.maxh + haf);
+ }
+ self.cursorfreezed = false;
+
+ var py = self.getScrollTop(true);
+ if (ny < 0 && py <= 0) return self.noticeCursor();
+ else if (ny > self.page.maxh && py >= self.page.maxh) {
+ self.checkContentSize();
+ return self.noticeCursor();
+ }
+
+ self.doScrollTop(ny);
+ };
+
+ this.doScrollLeftBy = function(stp, relative) {
+ var nx = 0;
+ if (relative) {
+ nx = Math.floor((self.scroll.x - stp) * self.scrollratio.x)
+ } else {
+ var sx = (self.timer) ? self.newscrollx : self.getScrollLeft(true);
+ nx = sx - stp;
+ }
+ if (self.bouncescroll) {
+ var haf = Math.round(self.view.w / 2);
+ if (nx < -haf) nx = -haf;
+ else if (nx > (self.page.maxw + haf)) nx = (self.page.maxw + haf);
+ }
+ self.cursorfreezed = false;
+
+ var px = self.getScrollLeft(true);
+ if (nx < 0 && px <= 0) return self.noticeCursor();
+ else if (nx > self.page.maxw && px >= self.page.maxw) return self.noticeCursor();
+
+ self.doScrollLeft(nx);
+ };
+
+ this.doScrollTo = function(pos, relative) {
+ var ny = (relative) ? Math.round(pos * self.scrollratio.y) : pos;
+ if (ny < 0) ny = 0;
+ else if (ny > self.page.maxh) ny = self.page.maxh;
+ self.cursorfreezed = false;
+ self.doScrollTop(pos);
+ };
+
+ this.checkContentSize = function() {
+ var pg = self.getContentSize();
+ if ((pg.h != self.page.h) || (pg.w != self.page.w)) self.resize(false, pg);
+ };
+
+ self.onscroll = function(e) {
+ if (self.rail.drag) return;
+ if (!self.cursorfreezed) {
+ self.synched('scroll', function() {
+ self.scroll.y = Math.round(self.getScrollTop() * (1 / self.scrollratio.y));
+ if (self.railh) self.scroll.x = Math.round(self.getScrollLeft() * (1 / self.scrollratio.x));
+ self.noticeCursor();
+ });
+ }
+ };
+ self.bind(self.docscroll, "scroll", self.onscroll);
+
+ this.doZoomIn = function(e) {
+ if (self.zoomactive) return;
+ self.zoomactive = true;
+
+ self.zoomrestore = {
+ style: {}
+ };
+ var lst = ['position', 'top', 'left', 'zIndex', 'backgroundColor', 'marginTop', 'marginBottom', 'marginLeft', 'marginRight'];
+ var win = self.win[0].style;
+ for (var a in lst) {
+ var pp = lst[a];
+ self.zoomrestore.style[pp] = (typeof win[pp] != "undefined") ? win[pp] : '';
+ }
+
+ self.zoomrestore.style.width = self.win.css('width');
+ self.zoomrestore.style.height = self.win.css('height');
+
+ self.zoomrestore.padding = {
+ w: self.win.outerWidth() - self.win.width(),
+ h: self.win.outerHeight() - self.win.height()
+ };
+
+ if (cap.isios4) {
+ self.zoomrestore.scrollTop = $(window).scrollTop();
+ $(window).scrollTop(0);
+ }
+
+ self.win.css({
+ "position": (cap.isios4) ? "absolute" : "fixed",
+ "top": 0,
+ "left": 0,
+ "z-index": globalmaxzindex + 100,
+ "margin": "0px"
+ });
+ var bkg = self.win.css("backgroundColor");
+ if (bkg == "" || /transparent|rgba\(0, 0, 0, 0\)|rgba\(0,0,0,0\)/.test(bkg)) self.win.css("backgroundColor", "#fff");
+ self.rail.css({
+ "z-index": globalmaxzindex + 101
+ });
+ self.zoom.css({
+ "z-index": globalmaxzindex + 102
+ });
+ self.zoom.css('backgroundPosition', '0px -18px');
+ self.resizeZoom();
+
+ if (self.onzoomin) self.onzoomin.call(self);
+
+ return self.cancelEvent(e);
+ };
+
+ this.doZoomOut = function(e) {
+ if (!self.zoomactive) return;
+ self.zoomactive = false;
+
+ self.win.css("margin", "");
+ self.win.css(self.zoomrestore.style);
+
+ if (cap.isios4) {
+ $(window).scrollTop(self.zoomrestore.scrollTop);
+ }
+
+ self.rail.css({
+ "z-index": self.zindex
+ });
+ self.zoom.css({
+ "z-index": self.zindex
+ });
+ self.zoomrestore = false;
+ self.zoom.css('backgroundPosition', '0px 0px');
+ self.onResize();
+
+ if (self.onzoomout) self.onzoomout.call(self);
+
+ return self.cancelEvent(e);
+ };
+
+ this.doZoom = function(e) {
+ return (self.zoomactive) ? self.doZoomOut(e) : self.doZoomIn(e);
+ };
+
+ this.resizeZoom = function() {
+ if (!self.zoomactive) return;
+
+ var py = self.getScrollTop(); //preserve scrolling position
+ self.win.css({
+ width: $(window).width() - self.zoomrestore.padding.w + "px",
+ height: $(window).height() - self.zoomrestore.padding.h + "px"
+ });
+ self.onResize();
+
+ self.setScrollTop(Math.min(self.page.maxh, py));
+ };
+
+ this.init();
+
+ $.nicescroll.push(this);
+
+ };
+
+ // Inspired by the work of Kin Blas
+ // http://webpro.host.adobe.com/people/jblas/momentum/includes/jquery.momentum.0.7.js
+
+
+ var ScrollMomentumClass2D = function(nc) {
+ var self = this;
+ this.nc = nc;
+
+ this.lastx = 0;
+ this.lasty = 0;
+ this.speedx = 0;
+ this.speedy = 0;
+ this.lasttime = 0;
+ this.steptime = 0;
+ this.snapx = false;
+ this.snapy = false;
+ this.demulx = 0;
+ this.demuly = 0;
+
+ this.lastscrollx = -1;
+ this.lastscrolly = -1;
+
+ this.chkx = 0;
+ this.chky = 0;
+
+ this.timer = 0;
+
+ this.time = function() {
+ return +new Date(); //beautifull hack
+ };
+
+ this.reset = function(px, py) {
+ self.stop();
+ var now = self.time();
+ self.steptime = 0;
+ self.lasttime = now;
+ self.speedx = 0;
+ self.speedy = 0;
+ self.lastx = px;
+ self.lasty = py;
+ self.lastscrollx = -1;
+ self.lastscrolly = -1;
+ };
+
+ this.update = function(px, py) {
+ var now = self.time();
+ self.steptime = now - self.lasttime;
+ self.lasttime = now;
+ var dy = py - self.lasty;
+ var dx = px - self.lastx;
+ var sy = self.nc.getScrollTop();
+ var sx = self.nc.getScrollLeft();
+ var newy = sy + dy;
+ var newx = sx + dx;
+ self.snapx = (newx < 0) || (newx > self.nc.page.maxw);
+ self.snapy = (newy < 0) || (newy > self.nc.page.maxh);
+ self.speedx = dx;
+ self.speedy = dy;
+ self.lastx = px;
+ self.lasty = py;
+ };
+
+ this.stop = function() {
+ self.nc.unsynched("domomentum2d");
+ if (self.timer) clearTimeout(self.timer);
+ self.timer = 0;
+ self.lastscrollx = -1;
+ self.lastscrolly = -1;
+ };
+
+ this.doSnapy = function(nx, ny) {
+ var snap = false;
+
+ if (ny < 0) {
+ ny = 0;
+ snap = true;
+ } else if (ny > self.nc.page.maxh) {
+ ny = self.nc.page.maxh;
+ snap = true;
+ }
+
+ if (nx < 0) {
+ nx = 0;
+ snap = true;
+ } else if (nx > self.nc.page.maxw) {
+ nx = self.nc.page.maxw;
+ snap = true;
+ }
+
+ (snap) ? self.nc.doScrollPos(nx, ny, self.nc.opt.snapbackspeed): self.nc.triggerScrollEnd();
+ };
+
+ this.doMomentum = function(gp) {
+ var t = self.time();
+ var l = (gp) ? t + gp : self.lasttime;
+
+ var sl = self.nc.getScrollLeft();
+ var st = self.nc.getScrollTop();
+
+ var pageh = self.nc.page.maxh;
+ var pagew = self.nc.page.maxw;
+
+ self.speedx = (pagew > 0) ? Math.min(60, self.speedx) : 0;
+ self.speedy = (pageh > 0) ? Math.min(60, self.speedy) : 0;
+
+ var chk = l && (t - l) <= 60;
+
+ if ((st < 0) || (st > pageh) || (sl < 0) || (sl > pagew)) chk = false;
+
+ var sy = (self.speedy && chk) ? self.speedy : false;
+ var sx = (self.speedx && chk) ? self.speedx : false;
+
+ if (sy || sx) {
+ var tm = Math.max(16, self.steptime); //timeout granularity
+
+ if (tm > 50) { // do smooth
+ var xm = tm / 50;
+ self.speedx *= xm;
+ self.speedy *= xm;
+ tm = 50;
+ }
+
+ self.demulxy = 0;
+
+ self.lastscrollx = self.nc.getScrollLeft();
+ self.chkx = self.lastscrollx;
+ self.lastscrolly = self.nc.getScrollTop();
+ self.chky = self.lastscrolly;
+
+ var nx = self.lastscrollx;
+ var ny = self.lastscrolly;
+
+ var onscroll = function() {
+ var df = ((self.time() - t) > 600) ? 0.04 : 0.02;
+
+ if (self.speedx) {
+ nx = Math.floor(self.lastscrollx - (self.speedx * (1 - self.demulxy)));
+ self.lastscrollx = nx;
+ if ((nx < 0) || (nx > pagew)) df = 0.10;
+ }
+
+ if (self.speedy) {
+ ny = Math.floor(self.lastscrolly - (self.speedy * (1 - self.demulxy)));
+ self.lastscrolly = ny;
+ if ((ny < 0) || (ny > pageh)) df = 0.10;
+ }
+
+ self.demulxy = Math.min(1, self.demulxy + df);
+
+ self.nc.synched("domomentum2d", function() {
+
+ if (self.speedx) {
+ var scx = self.nc.getScrollLeft();
+ if (scx != self.chkx) self.stop();
+ self.chkx = nx;
+ self.nc.setScrollLeft(nx);
+ }
+
+ if (self.speedy) {
+ var scy = self.nc.getScrollTop();
+ if (scy != self.chky) self.stop();
+ self.chky = ny;
+ self.nc.setScrollTop(ny);
+ }
+
+ if (!self.timer) {
+ self.nc.hideCursor();
+ self.doSnapy(nx, ny);
+ }
+
+ });
+
+ if (self.demulxy < 1) {
+ self.timer = setTimeout(onscroll, tm);
+ } else {
+ self.stop();
+ self.nc.hideCursor();
+ self.doSnapy(nx, ny);
+ }
+ };
+
+ onscroll();
+
+ } else {
+ self.doSnapy(self.nc.getScrollLeft(), self.nc.getScrollTop());
+ }
+
+ }
+
+ };
+
+
+ // override jQuery scrollTop
+
+ var _scrollTop = jQuery.fn.scrollTop; // preserve original function
+
+ jQuery.cssHooks["pageYOffset"] = {
+ get: function(elem, computed, extra) {
+ var nice = $.data(elem, '__nicescroll') || false;
+ return (nice && nice.ishwscroll) ? nice.getScrollTop() : _scrollTop.call(elem);
+ },
+ set: function(elem, value) {
+ var nice = $.data(elem, '__nicescroll') || false;
+ (nice && nice.ishwscroll) ? nice.setScrollTop(parseInt(value)): _scrollTop.call(elem, value);
+ return this;
+ }
+ };
+
+ /*
+ $.fx.step["scrollTop"] = function(fx){
+ $.cssHooks["scrollTop"].set( fx.elem, fx.now + fx.unit );
+ };
+*/
+
+ jQuery.fn.scrollTop = function(value) {
+ if (typeof value == "undefined") {
+ var nice = (this[0]) ? $.data(this[0], '__nicescroll') || false : false;
+ return (nice && nice.ishwscroll) ? nice.getScrollTop() : _scrollTop.call(this);
+ } else {
+ return this.each(function() {
+ var nice = $.data(this, '__nicescroll') || false;
+ (nice && nice.ishwscroll) ? nice.setScrollTop(parseInt(value)): _scrollTop.call($(this), value);
+ });
+ }
+ };
+
+ // override jQuery scrollLeft
+
+ var _scrollLeft = jQuery.fn.scrollLeft; // preserve original function
+
+ $.cssHooks.pageXOffset = {
+ get: function(elem, computed, extra) {
+ var nice = $.data(elem, '__nicescroll') || false;
+ return (nice && nice.ishwscroll) ? nice.getScrollLeft() : _scrollLeft.call(elem);
+ },
+ set: function(elem, value) {
+ var nice = $.data(elem, '__nicescroll') || false;
+ (nice && nice.ishwscroll) ? nice.setScrollLeft(parseInt(value)): _scrollLeft.call(elem, value);
+ return this;
+ }
+ };
+
+ /*
+ $.fx.step["scrollLeft"] = function(fx){
+ $.cssHooks["scrollLeft"].set( fx.elem, fx.now + fx.unit );
+ };
+*/
+
+ jQuery.fn.scrollLeft = function(value) {
+ if (typeof value == "undefined") {
+ var nice = (this[0]) ? $.data(this[0], '__nicescroll') || false : false;
+ return (nice && nice.ishwscroll) ? nice.getScrollLeft() : _scrollLeft.call(this);
+ } else {
+ return this.each(function() {
+ var nice = $.data(this, '__nicescroll') || false;
+ (nice && nice.ishwscroll) ? nice.setScrollLeft(parseInt(value)): _scrollLeft.call($(this), value);
+ });
+ }
+ };
+
+ var NiceScrollArray = function(doms) {
+ var self = this;
+ this.length = 0;
+ this.name = "nicescrollarray";
+
+ this.each = function(fn) {
+ for (var a = 0, i = 0; a < self.length; a++) fn.call(self[a], i++);
+ return self;
+ };
+
+ this.push = function(nice) {
+ self[self.length] = nice;
+ self.length++;
+ };
+
+ this.eq = function(idx) {
+ return self[idx];
+ };
+
+ if (doms) {
+ for (var a = 0; a < doms.length; a++) {
+ var nice = $.data(doms[a], '__nicescroll') || false;
+ if (nice) {
+ this[this.length] = nice;
+ this.length++;
+ }
+ };
+ }
+
+ return this;
+ };
+
+ function mplex(el, lst, fn) {
+ for (var a = 0; a < lst.length; a++) fn(el, lst[a]);
+ };
+ mplex(
+ NiceScrollArray.prototype, ['show', 'hide', 'toggle', 'onResize', 'resize', 'remove', 'stop', 'doScrollPos'],
+ function(e, n) {
+ e[n] = function() {
+ var args = arguments;
+ return this.each(function() {
+ this[n].apply(this, args);
+ });
+ };
+ }
+ );
+
+ jQuery.fn.getNiceScroll = function(index) {
+ if (typeof index == "undefined") {
+ return new NiceScrollArray(this);
+ } else {
+ var nice = this[index] && $.data(this[index], '__nicescroll') || false;
+ return nice;
+ }
+ };
+
+ jQuery.extend(jQuery.expr[':'], {
+ nicescroll: function(a) {
+ return ($.data(a, '__nicescroll')) ? true : false;
+ }
+ });
+
+ $.fn.niceScroll = function(wrapper, opt) {
+ if (typeof opt == "undefined") {
+ if ((typeof wrapper == "object") && !("jquery" in wrapper)) {
+ opt = wrapper;
+ wrapper = false;
+ }
+ }
+ opt = $.extend({},opt); // cloning
+ var ret = new NiceScrollArray();
+ if (typeof opt == "undefined") opt = {};
+
+ if (wrapper || false) {
+ opt.doc = $(wrapper);
+ opt.win = $(this);
+ }
+ var docundef = !("doc" in opt);
+ if (!docundef && !("win" in opt)) opt.win = $(this);
+
+ this.each(function() {
+ var nice = $(this).data('__nicescroll') || false;
+ if (!nice) {
+ opt.doc = (docundef) ? $(this) : opt.doc;
+ nice = new NiceScrollClass(opt, $(this));
+ $(this).data('__nicescroll', nice);
+ }
+ ret.push(nice);
+ });
+ return (ret.length == 1) ? ret[0] : ret;
+ };
+
+ window.NiceScroll = {
+ getjQuery: function() {
+ return jQuery
+ }
+ };
+
+ if (!$.nicescroll) {
+ $.nicescroll = new NiceScrollArray();
+ $.nicescroll.options = _globaloptions;
+ }
+
+}));
\ No newline at end of file