Commit 4c8a6b61 authored by Regis's avatar Regis

Merge branch 'master' into auto-pipelines-vue

parents 243717c0 dd5e46ab
/public/
/tmp/
/vendor/
{
"extends": "airbnb",
"globals": {
"$": false,
"_": false,
"beforeEach": false,
"d3": false,
"define": false,
"describe": false,
"document": false,
"expect": false,
"fixture": false,
"gl": false,
"it": false,
"jQuery": false,
"Mousetrap": false,
"spyOn": false,
"spyOnEvent": false,
"Turbolinks": false,
"window": false
}
}
......@@ -37,6 +37,7 @@
/doc/code/*
/dump.rdb
/log/*.log*
/node_modules/
/nohup.out
/public/assets/
/public/uploads.*
......
......@@ -215,6 +215,7 @@ rake ee_compat_check:
only:
- branches
except:
- master
- tags
allow_failure: yes
......@@ -310,16 +311,29 @@ coverage:
- coverage/index.html
- coverage/assets/
lint-javascript:
stage: test
image: "node:latest"
before_script:
- npm install
script:
- npm run eslint
# Trigger docs build
# https://gitlab.com/gitlab-com/doc-gitlab-com/blob/master/README.md#deployment-process
trigger_docs:
stage: post-test
before_script: []
image: "alpine"
before_script:
- apk update && apk add curl
variables:
GIT_STRATEGY: none
cache: {}
artifacts: {}
script:
- "curl -X POST -F token=${DOCS_TRIGGER_TOKEN} -F ref=master https://gitlab.com/api/v3/projects/38069/trigger/builds"
- "curl -X POST -F token=${DOCS_TRIGGER_TOKEN} -F ref=master -F variables[PROJECT]=ce https://gitlab.com/api/v3/projects/38069/trigger/builds"
only:
- master
- master@gitlab-org/gitlab-ce
# Notify slack in the end
......
......@@ -8,30 +8,45 @@ Please view this file on the master branch, on stable branches it's out of date.
- Fix extra space on Build sidebar on Firefox !7060
- Fix HipChat notifications rendering (airatshigapov, eisnerd)
- Add hover to trash icon in notes !7008 (blackst0ne)
- Fix sidekiq stats in admin area (blackst0ne)
- Escape ref and path for relative links !6050 (winniehell)
- Fixed link typo on /help/ui to Alerts section. !6915 (Sam Rose)
- Fix filtering of milestones with quotes in title (airatshigapov)
- Refactor less readable existance checking code from CoffeeScript !6289 (jlogandavison)
- Simpler arguments passed to named_route on toggle_award_url helper method
- Fix typo in framework css class. !7086 (Daniel Voogsgerd)
- Fix: Backup restore doesn't clear cache
- API: Fix project deploy keys 400 and 500 errors when adding an existing key. !6784 (Joshua Welsh)
- Replace jquery.cookie plugin with js.cookie !7085
- Use MergeRequestsClosingIssues cache data on Issue#closed_by_merge_requests method
- Fix Sign in page 'Forgot your password?' link overlaps on medium-large screens
- Fix documents and comments on Build API `scope`
- Refactor email, use setter method instead AR callbacks for email attribute (Semyon Pupkov)
## 8.13.1 (unreleased)
- Fix bug where labels would be assigned to issues that were moved
- Fix error in generating labels
- Fix reply-by-email not working due to queue name mismatch
- Fixed hidden pipeline graph on commit and MR page !6895
- Expire and build repository cache after project import
- Reduce overhead of LabelFinder by avoiding #presence call !7094
- Fix 404 for group pages when GitLab setup uses relative url
- Simpler arguments passed to named_route on toggle_award_url helper method
- Fix unauthorized users dragging on issue boards
- Better handle when no users were selected for adding to group or project. (Linus Thiel)
- Only show register tab if signup enabled.
- Only schedule ProjectCacheWorker jobs when needed
## 8.13.1 (2016-10-25)
- Fix branch protection API. !6215
- Fix hidden pipeline graph on commit and MR page. !6895
- Fix Cycle analytics not showing correct data when filtering by date. !6906
- Ensure custom provider tab labels don't break layout. !6993
- Fix issue boards user link when in subdirectory. !7018
- Refactor and add new environment functionality to CI yaml reference. !7026
- Fix typo in project settings that prevents users from enabling container registry. !7037
- Fix events order in `users/:id/events` endpoint. !7039
- Remove extra line for empty issue description. !7045
- Don't append issue/MR templates to any existing text. !7050
- Fix error in generating labels. !7055
- Stop clearing the database cache on `rake cache:clear`. !7056
- Only show register tab if signup enabled. !7058
- Expire and build repository cache after project import. !7064
- Fix bug where labels would be assigned to issues that were moved. !7065
- Fix reply-by-email not working due to queue name mismatch. !7068
- Fix 404 for group pages when GitLab setup uses relative url. !7071
- Fix `User#to_reference`. !7088
- Reduce overhead of `LabelFinder` by avoiding `#presence` call. !7094
- Fix unauthorized users dragging on issue boards. !7096
- Only schedule `ProjectCacheWorker` jobs when needed. !7099
## 8.13.0 (2016-10-22)
- Removes extra line for empty issue description. (!7045)
- Fix save button on project pipeline settings page. (!6955)
- All Sidekiq workers now use their own queue
- Avoid race condition when asynchronously removing expired artifacts. (!6881)
......@@ -52,7 +67,6 @@ Please view this file on the master branch, on stable branches it's out of date.
- Update duration at the end of pipeline
- ExpireBuildArtifactsWorker query builds table without ordering enqueuing one job per build to cleanup
- Add group level labels. (!6425)
- Fix Cycle analytics not showing correct data when filtering by date. !6906
- Add an example for testing a phoenix application with Gitlab CI in the docs (Manthan Mallikarjun)
- Cancelled pipelines could be retried. !6927
- Updating verbiage on git basics to be more intuitive
......@@ -60,7 +74,6 @@ Please view this file on the master branch, on stable branches it's out of date.
- Clarify documentation for Runners API (Gennady Trafimenkov)
- The instrumentation for Banzai::Renderer has been restored
- Change user & group landing page routing from /u/:username to /:username
- Fixed issue boards user link when in subdirectory
- Added documentation for .gitattributes files
- Move Pipeline Metrics to separate worker
- AbstractReferenceFilter caches project_refs on RequestStore when active
......@@ -407,7 +420,6 @@ Please view this file on the master branch, on stable branches it's out of date.
- Fix inconsistent checkbox alignment (ClemMakesApps)
- Use the default branch for displaying the project icon instead of master !5792 (Hannes Rosenögger)
- Adds response mime type to transaction metric action when it's not HTML
- Fix branch protection API !6215
- Fix hover leading space bug in pipeline graph !5980
- Avoid conflict with admin labels when importing GitHub labels
- User can edit closed MR with deleted fork (Katarzyna Kobierska Ula Budziszewska) !5496
......
/* eslint-disable */
((global) => {
const MAX_MESSAGE_LENGTH = 500;
const MESSAGE_CELL_SELECTOR = '.abuse-reports .message';
......
/* eslint-disable */
(function() {
this.Activities = (function() {
function Activities() {
......@@ -24,9 +25,7 @@
var filter = sender.attr("id").split("_")[0];
$('.event-filter .active').removeClass("active");
$.cookie("event_filter", filter, {
path: gon.relative_url_root || '/'
});
Cookies.set("event_filter", filter);
sender.closest('li').toggleClass("active");
};
......
/* eslint-disable */
(function() {
this.Admin = (function() {
function Admin() {
......
/* eslint-disable */
(function() {
this.Api = {
groupsPath: "/api/:version/groups.json",
......
/* eslint-disable */
// This is a manifest file that'll be compiled into including all the files listed below.
// Add new JavaScript/Coffee code in separate files in this directory and they'll automatically
// be included in the compiled file accessible from http://example.com/assets/application.js
......@@ -11,13 +12,13 @@
/*= require jquery-ui/effect-highlight */
/*= require jquery-ui/sortable */
/*= require jquery_ujs */
/*= require jquery.cookie */
/*= require jquery.endless-scroll */
/*= require jquery.highlight */
/*= require jquery.waitforimages */
/*= require jquery.atwho */
/*= require jquery.scrollTo */
/*= require jquery.turbolinks */
/*= require js.cookie */
/*= require turbolinks */
/*= require autosave */
/*= require bootstrap/affix */
......@@ -145,6 +146,10 @@
$document = $(document);
$window = $(window);
$body = $('body');
// Set the default path for all cookies to GitLab's root directory
Cookies.defaults.path = gon.relative_url_root || '/';
gl.utils.preventDisabledButtons();
bootstrapBreakpoint = bp.getBreakpointSize();
$(".nav-sidebar").niceScroll({
......
/* eslint-disable */
(function() {
this.Aside = (function() {
function Aside() {
......
/* eslint-disable */
(function() {
this.Autosave = (function() {
function Autosave(field, key) {
......
/* eslint-disable */
(function() {
this.AwardsHandler = (function() {
const FROM_SENTENCE_REGEX = /(?:, and | and |, )/; //For separating lists produced by ruby's Array#toSentence
......@@ -91,7 +92,7 @@
css = {
top: ($addBtn.offset().top + $addBtn.outerHeight()) + "px"
};
if ((position != null) && position === 'right') {
if (position === 'right') {
css.left = (($addBtn.offset().left - $menu.outerWidth()) + 20) + "px";
$menu.addClass('is-aligned-right');
} else {
......@@ -322,21 +323,18 @@
var frequentlyUsedEmojis;
frequentlyUsedEmojis = this.getFrequentlyUsedEmojis();
frequentlyUsedEmojis.push(emoji);
return $.cookie('frequently_used_emojis', frequentlyUsedEmojis.join(','), {
path: gon.relative_url_root || '/',
expires: 365
});
Cookies.set('frequently_used_emojis', frequentlyUsedEmojis.join(','), { expires: 365 });
};
AwardsHandler.prototype.getFrequentlyUsedEmojis = function() {
var frequentlyUsedEmojis;
frequentlyUsedEmojis = ($.cookie('frequently_used_emojis') || '').split(',');
frequentlyUsedEmojis = (Cookies.get('frequently_used_emojis') || '').split(',');
return _.compact(_.uniq(frequentlyUsedEmojis));
};
AwardsHandler.prototype.renderFrequentlyUsedBlock = function() {
var emoji, frequentlyUsedEmojis, i, len, ul;
if ($.cookie('frequently_used_emojis')) {
if (Cookies.get('frequently_used_emojis')) {
frequentlyUsedEmojis = this.getFrequentlyUsedEmojis();
ul = $("<ul class='clearfix emoji-menu-list frequent-emojis'>");
for (i = 0, len = frequentlyUsedEmojis.length; i < len; i++) {
......
/* eslint-disable */
/*= require jquery.ba-resize */
/*= require autosize */
......
/* eslint-disable */
(function() {
$(function() {
$("body").on("click", ".js-details-target", function() {
......
/* eslint-disable */
// Quick Submit behavior
//
// When a child field of a form with a `js-quick-submit` class receives a
......
/* eslint-disable */
// Requires Input behavior
//
// When called on a form with input fields with the `required` attribute, the
......
/* eslint-disable */
(function(w) {
$(function() {
// Toggle button. Show/hide content inside parent container.
......
/* eslint-disable */
/*= require blob/template_selector */
((global) => {
......
/* eslint-disable */
(function() {
this.BlobFileDropzone = (function() {
function BlobFileDropzone(form, method) {
......
/* eslint-disable */
/*= require blob/template_selector */
......
/* eslint-disable */
(function() {
this.BlobGitignoreSelectors = (function() {
function BlobGitignoreSelectors(opts) {
......
/* eslint-disable */
/*= require blob/template_selector */
......
/* eslint-disable */
((global) => {
class BlobLicenseSelectors {
constructor({ $dropdowns, editor }) {
......
/* eslint-disable */
((global) => {
class TemplateSelector {
constructor({ dropdown, data, pattern, wrapper, editor, fileEndpoint, $input } = {}) {
......
/* eslint-disable */
/*= require_tree . */
(function() {
......
/* eslint-disable */
(function() {
var bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; };
......
/* eslint-disable */
//= require vue
//= require vue-resource
//= require Sortable
......@@ -5,7 +6,9 @@
//= require_tree ./stores
//= require_tree ./services
//= require_tree ./mixins
//= require_tree ./filters
//= require ./components/board
//= require ./components/board_sidebar
//= require ./components/new_list_dropdown
//= require ./vue_resource_interceptor
......@@ -22,7 +25,8 @@ $(() => {
gl.IssueBoardsApp = new Vue({
el: $boardApp,
components: {
'board': gl.issueBoards.Board
'board': gl.issueBoards.Board,
'board-sidebar': gl.issueBoards.BoardSidebar
},
data: {
state: Store.state,
......@@ -30,9 +34,15 @@ $(() => {
endpoint: $boardApp.dataset.endpoint,
boardId: $boardApp.dataset.boardId,
disabled: $boardApp.dataset.disabled === 'true',
issueLinkBase: $boardApp.dataset.issueLinkBase
issueLinkBase: $boardApp.dataset.issueLinkBase,
detailIssue: Store.detail
},
init: Store.create.bind(Store),
computed: {
detailIssueVisible () {
return Object.keys(this.detailIssue.issue).length;
}
},
created () {
gl.boardService = new BoardService(this.endpoint, this.boardId);
},
......
/* eslint-disable */
//= require ./board_blank_state
//= require ./board_delete
//= require ./board_list
......@@ -21,6 +22,7 @@
},
data () {
return {
detailIssue: Store.detail,
filters: Store.state.filters,
showIssueForm: false
};
......@@ -32,6 +34,26 @@
this.list.getIssues(true);
},
deep: true
},
detailIssue: {
handler () {
if (!Object.keys(this.detailIssue.issue).length) return;
const issue = this.list.findIssue(this.detailIssue.issue.id);
if (issue) {
const boardsList = document.querySelectorAll('.boards-list')[0];
const right = (this.$el.offsetLeft + this.$el.offsetWidth) - boardsList.offsetWidth;
const left = boardsList.scrollLeft - this.$el.offsetLeft;
if (right - boardsList.scrollLeft > 0) {
boardsList.scrollLeft = right;
} else if (left > 0) {
boardsList.scrollLeft = this.$el.offsetLeft;
}
}
},
deep: true
}
},
methods: {
......
/* eslint-disable */
(() => {
const Store = gl.issueBoards.BoardsStore;
......
/* eslint-disable */
(() => {
const Store = gl.issueBoards.BoardsStore;
......@@ -12,6 +13,17 @@
disabled: Boolean,
index: Number
},
data () {
return {
showDetail: false,
detailIssue: Store.detail
};
},
computed: {
issueDetailVisible () {
return this.detailIssue.issue && this.detailIssue.issue.id === this.issue.id;
}
},
methods: {
filterByLabel (label, e) {
let labelToggleText = label.title;
......@@ -37,6 +49,29 @@
$('.labels-filter .dropdown-toggle-text').text(labelToggleText);
Store.updateFiltersUrl();
},
mouseDown () {
this.showDetail = true;
},
mouseMove () {
if (this.showDetail) {
this.showDetail = false;
}
},
showIssue (e) {
const targetTagName = e.target.tagName.toLowerCase();
if (targetTagName === 'a' || targetTagName === 'button') return;
if (this.showDetail) {
this.showDetail = false;
if (Store.detail.issue && Store.detail.issue.id === this.issue.id) {
Store.detail.issue = {};
} else {
Store.detail.issue = this.issue;
}
}
}
}
});
......
/* eslint-disable */
(() => {
window.gl = window.gl || {};
window.gl.issueBoards = window.gl.issueBoards || {};
......
/* eslint-disable */
//= require ./board_card
//= require ./board_new_issue
......
/* eslint-disable */
(() => {
const Store = gl.issueBoards.BoardsStore;
window.gl = window.gl || {};
gl.issueBoards.BoardNewIssue = Vue.extend({
......@@ -27,13 +30,16 @@
const labels = this.list.label ? [this.list.label] : [];
const issue = new ListIssue({
title: this.title,
labels
labels,
subscribed: true
});
this.list.newIssue(issue)
.then((data) => {
// Need this because our jQuery very kindly disables buttons on ALL form submissions
$(this.$els.submitButton).enable();
Store.detail.issue = issue;
})
.catch(() => {
// Need this because our jQuery very kindly disables buttons on ALL form submissions
......
(() => {
const Store = gl.issueBoards.BoardsStore;
window.gl = window.gl || {};
window.gl.issueBoards = window.gl.issueBoards || {};
gl.issueBoards.BoardSidebar = Vue.extend({
props: {
currentUser: Object
},
data() {
return {
detail: Store.detail,
issue: {}
};
},
computed: {
showSidebar () {
return Object.keys(this.issue).length;
}
},
watch: {
detail: {
handler () {
this.issue = this.detail.issue;
},
deep: true
},
issue () {
if (this.showSidebar) {
this.$nextTick(() => {
$('.right-sidebar').getNiceScroll(0).doScrollTop(0, 0);
$('.right-sidebar').getNiceScroll().resize();
});
}
}
},
methods: {
closeSidebar () {
this.detail.issue = {};
}
},
ready () {
new IssuableContext(this.currentUser);
new MilestoneSelect();
new gl.DueDateSelectors();
new LabelsSelect();
new Sidebar();
new Subscription('.subscription');
}
});
})();
/* eslint-disable */
$(() => {
const Store = gl.issueBoards.BoardsStore;
......
Vue.filter('due-date', (value) => {
const date = new Date(value);
return $.datepicker.formatDate('M d, yy', date);
});
/* eslint-disable */
((w) => {
window.gl = window.gl || {};
window.gl.issueBoards = window.gl.issueBoards || {};
......@@ -22,7 +23,7 @@
fallbackOnBody: true,
ghostClass: 'is-ghost',
filter: '.has-tooltip, .btn',
delay: gl.issueBoards.touchEnabled ? 100 : 0,
delay: gl.issueBoards.touchEnabled ? 100 : 50,
scrollSensitivity: gl.issueBoards.touchEnabled ? 60 : 100,
scrollSpeed: 20,
onStart: gl.issueBoards.onStart,
......
/* eslint-disable */
class ListIssue {
constructor (obj) {
this.id = obj.iid;
this.title = obj.title;
this.confidential = obj.confidential;
this.dueDate = obj.due_date;
this.subscribed = obj.subscribed;
this.labels = [];
if (obj.assignee) {
this.assignee = new ListUser(obj.assignee);
}
if (obj.milestone) {
this.milestone = new ListMilestone(obj.milestone);
}
obj.labels.forEach((label) => {
this.labels.push(new ListLabel(label));
});
......@@ -41,4 +48,21 @@ class ListIssue {
getLists () {
return gl.issueBoards.BoardsStore.state.lists.filter( list => list.findIssue(this.id) );
}
update (url) {
const data = {
issue: {
milestone_id: this.milestone ? this.milestone.id : null,
due_date: this.dueDate,
assignee_id: this.assignee ? this.assignee.id : null,
label_ids: this.labels.map( (label) => label.id )
}
};
if (!data.issue.label_ids.length) {
data.issue.label_ids = [''];
}
return Vue.http.patch(url, data);
}
}
/* eslint-disable */
class ListLabel {
constructor (obj) {
this.id = obj.id;
......
/* eslint-disable */
class List {
constructor (obj) {
this.id = obj.id;
......
class ListMilestone {
constructor (obj) {
this.id = obj.id;
this.title = obj.title;
}
}
/* eslint-disable */
class ListUser {
constructor (user) {
this.id = user.id;
......
/* eslint-disable */
class BoardService {
constructor (root, boardId) {
Vue.http.options.root = root;
this.lists = Vue.resource(`${root}/${boardId}/lists{/id}`, {}, {
generate: {
method: 'POST',
......
/* eslint-disable */
(() => {
window.gl = window.gl || {};
window.gl.issueBoards = window.gl.issueBoards || {};
......@@ -5,6 +6,9 @@
gl.issueBoards.BoardsStore = {
disabled: false,
state: {},
detail: {
issue: {}
},
moving: {
issue: {},
list: {}
......@@ -58,12 +62,12 @@
removeBlankState () {
this.removeList('blank');
$.cookie('issue_board_welcome_hidden', 'true', {
Cookies.set('issue_board_welcome_hidden', 'true', {
expires: 365 * 10
});
},
welcomeIsHidden () {
return $.cookie('issue_board_welcome_hidden') === 'true';
return Cookies.get('issue_board_welcome_hidden') === 'true';
},
removeList (id, type = 'blank') {
const list = this.findList('id', id, type);
......
/* eslint-disable */
(function () {
'use strict';
......
/* eslint-disable */
Vue.http.interceptors.push((request, next) => {
Vue.activeResources = Vue.activeResources ? Vue.activeResources + 1 : 1;
......
/* eslint-disable */
(function() {
this.Breakpoints = (function() {
var BreakpointInstance, instance;
......
/* eslint-disable */
(function() {
$(function() {
var previewPath;
......
/* eslint-disable */
(function() {
var bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; };
......
/* eslint-disable */
(function() {
this.BuildArtifacts = (function() {
function BuildArtifacts() {
......
/* eslint-disable */
$(function(){
$('.reveal-variables').off('click').on('click',function(){
$('.js-build').toggle().niceScroll();
......
/* eslint-disable */
(function() {
this.Commit = (function() {
function Commit() {
......
/* eslint-disable */
(function() {
this.CommitFile = (function() {
function CommitFile(file) {
......
/* eslint-disable */
(function() {
this.ImageFile = (function() {
var prepareFrames;
......
/* eslint-disable */
(function() {
this.CommitsList = (function() {
function CommitsList() {}
......
/* eslint-disable */
(function() {
this.Compare = (function() {
function Compare(opts) {
......
/* eslint-disable */
(function() {
this.CompareAutocomplete = (function() {
function CompareAutocomplete() {
......
/* eslint-disable */
(function() {
this.ConfirmDangerModal = (function() {
function ConfirmDangerModal(form, text) {
......
/* eslint-disable */
/*= require clipboard */
......
/* eslint-disable */
(function (w) {
class CreateLabelDropdown {
constructor ($el, namespacePath, projectPath) {
......
/* eslint-disable */
//= require vue
((global) => {
......@@ -6,7 +7,7 @@
const store = gl.cycleAnalyticsStore = {
isLoading: true,
hasError: false,
isHelpDismissed: $.cookie(COOKIE_NAME),
isHelpDismissed: Cookies.get(COOKIE_NAME),
analytics: {}
};
......@@ -75,9 +76,7 @@
dismissLanding() {
store.isHelpDismissed = true;
$.cookie(COOKIE_NAME, true, {
path: gon.relative_url_root || '/'
});
Cookies.set(COOKIE_NAME, true);
}
initDropdown() {
......
/* eslint-disable */
(function() {
this.Diff = (function() {
var UNFOLD_COUNT;
......
/* eslint-disable */
((w) => {
w.CommentAndResolveBtn = Vue.extend({
props: {
......
/* eslint-disable */
(() => {
JumpToDiscussion = Vue.extend({
mixins: [DiscussionMixins],
......
/* eslint-disable */
((w) => {
w.ResolveBtn = Vue.extend({
props: {
......
/* eslint-disable */
((w) => {
w.ResolveCount = Vue.extend({
mixins: [DiscussionMixins],
......
/* eslint-disable */
((w) => {
w.ResolveDiscussionBtn = Vue.extend({
props: {
......
/* eslint-disable */
//= require vue
//= require vue-resource
//= require_directory ./models
......
/* eslint-disable */
((w) => {
w.DiscussionMixins = {
computed: {
......
/* eslint-disable */
class DiscussionModel {
constructor (discussionId) {
this.id = discussionId;
......
/* eslint-disable */
class NoteModel {
constructor (discussionId, noteId, canResolve, resolved, resolved_by) {
this.discussionId = discussionId;
......
/* eslint-disable */
((w) => {
class ResolveServiceClass {
constructor() {
......
/* eslint-disable */
((w) => {
w.CommentsStore = {
state: {},
......
/* eslint-disable */
(function() {
var Dispatcher;
......
/* eslint-disable */
/*= require preview_markdown */
......
/* eslint-disable */
(function(global) {
class DueDateSelect {
constructor({ $dropdown, $loading } = {}) {
......@@ -41,16 +42,27 @@
defaultDate: $("input[name='" + this.fieldName + "']").val(),
altField: "input[name='" + this.fieldName + "']",
onSelect: () => {
if (this.$dropdown.hasClass('js-issue-boards-due-date')) {
gl.issueBoards.BoardsStore.detail.issue.dueDate = $(`input[name='${this.fieldName}']`).val();
this.updateIssueBoardIssue();
} else {
return this.saveDueDate(true);
}
}
});
}
initRemoveDueDate() {
this.$block.on('click', '.js-remove-due-date', (e) => {
e.preventDefault();
if (this.$dropdown.hasClass('js-issue-boards-due-date')) {
gl.issueBoards.BoardsStore.detail.issue.dueDate = '';
this.updateIssueBoardIssue();
} else {
$("input[name='" + this.fieldName + "']").val('');
return this.saveDueDate(false);
}
});
}
......@@ -83,6 +95,18 @@
this.datePayload = datePayload;
}
updateIssueBoardIssue () {
this.$loading.fadeIn();
this.$dropdown.trigger('loading.gl.dropdown');
this.$selectbox.hide();
this.$value.css('display', '');
gl.issueBoards.BoardsStore.detail.issue.update(this.$dropdown.attr('data-issue-update'))
.then(() => {
this.$loading.fadeOut();
});
}
submitSelectedDate(isDropdown) {
return $.ajax({
type: 'PUT',
......
/* eslint-disable */
Array.prototype.first = function() {
return this[0];
}
......
/* eslint-disable */
Element.prototype.matches = Element.prototype.matches || Element.prototype.msMatches;
Element.prototype.closest = function closest(selector, selectedElement = this) {
......
/* eslint-disable */
// Disable an element and add the 'disabled' Bootstrap class
(function() {
$.fn.extend({
......
/* eslint-disable */
(function() {
var bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; };
......
/* eslint-disable */
(function() {
this.Flash = (function() {
var hideFlash;
......
/* eslint-disable */
// Creates the variables for setting up GFM auto-completion
(function() {
if (window.GitLab == null) {
......
/* eslint-disable */
(function() {
var GitLabDropdown, GitLabDropdownFilter, GitLabDropdownRemote,
bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; },
......@@ -208,7 +209,7 @@
FILTER_INPUT = '.dropdown-input .dropdown-input-field';
function GitLabDropdown(el1, options) {
var ref, ref1, ref2, ref3, searchFields, selector, self;
var searchFields, selector, self;
this.el = el1;
this.options = options;
this.updateLabel = bind(this.updateLabel, this);
......@@ -219,7 +220,11 @@
selector = $(this.el).data("target");
this.dropdown = selector != null ? $(selector) : $(this.el).parent();
// Set Defaults
ref = this.options, this.filterInput = (ref1 = ref.filterInput) != null ? ref1 : this.getElement(FILTER_INPUT), this.highlight = (ref2 = ref.highlight) != null ? ref2 : false, this.filterInputBlur = (ref3 = ref.filterInputBlur) != null ? ref3 : true;
this.filterInput = this.options.filterInput || this.getElement(FILTER_INPUT);
this.highlight = !!this.options.highlight
this.filterInputBlur = this.options.filterInputBlur != null
? this.options.filterInputBlur
: true;
// If no input is passed create a default one
self = this;
// If selector was passed
......@@ -418,7 +423,9 @@
var $target;
if (this.options.multiSelect) {
$target = $(e.target);
if ($target && !$target.hasClass('dropdown-menu-close') && !$target.hasClass('dropdown-menu-close-icon') && !$target.data('is-link')) {
if ($target && !$target.hasClass('dropdown-menu-close') &&
!$target.hasClass('dropdown-menu-close-icon') &&
!$target.data('is-link')) {
e.stopPropagation();
return false;
} else {
......@@ -549,6 +556,8 @@
value = this.options.id ? this.options.id(data) : data.id;
fieldName = this.options.fieldName;
if (value) { value = value.toString().replace(/'/g, '\\\'') };
field = this.dropdown.parent().find("input[name='" + fieldName + "'][value='" + value + "']");
if (field.length) {
selected = true;
......@@ -620,8 +629,21 @@
selectedObject = this.renderedData[selectedIndex];
}
}
if (this.options.vue) {
if (el.hasClass(ACTIVE_CLASS)) {
el.removeClass(ACTIVE_CLASS);
} else {
el.addClass(ACTIVE_CLASS);
}
return selectedObject;
}
field = [];
value = this.options.id ? this.options.id(selectedObject, el) : selectedObject.id;
value = this.options.id
? this.options.id(selectedObject, el)
: selectedObject.id;
if (isInput) {
field = $(this.el);
} else if(value) {
......
/* eslint-disable */
((global) => {
/*
* This class overrides the browser's validation error bubbles, displaying custom
......
/* eslint-disable */
(function() {
this.GLForm = (function() {
function GLForm(form) {
......
/* eslint-disable */
// This is a manifest file that'll be compiled into including all the files listed below.
// Add new JavaScript/Coffee code in separate files in this directory and they'll automatically
// be included in the compiled file accessible from http://example.com/assets/application.js
......
/* eslint-disable */
(function() {
this.StatGraph = (function() {
function StatGraph() {}
......
/* eslint-disable */
/*= require d3 */
......@@ -29,8 +30,7 @@
ContributorsGraph.set_y_domain = function(data) {
return ContributorsGraph.prototype.y_domain = [
0, d3.max(data, function(d) {
var ref, ref1;
return d.commits = (ref = (ref1 = d.commits) != null ? ref1 : d.additions) != null ? ref : d.deletions;
return d.commits = d.commits || d.additions || d.deletions;
})
];
};
......@@ -44,8 +44,7 @@
ContributorsGraph.init_y_domain = function(data) {
return ContributorsGraph.prototype.y_domain = [
0, d3.max(data, function(d) {
var ref, ref1;
return d.commits = (ref = (ref1 = d.commits) != null ? ref1 : d.additions) != null ? ref : d.deletions;
return d.commits = d.commits || d.additions || d.deletions;
})
];
};
......@@ -147,9 +146,8 @@
return this.area = d3.svg.area().x(function(d) {
return x(d.date);
}).y0(this.height).y1(function(d) {
var ref, ref1, xa;
xa = d.commits = (ref = (ref1 = d.commits) != null ? ref1 : d.additions) != null ? ref : d.deletions;
return y(xa);
d.commits = d.commits || d.additions || d.deletions;
return y(d.commits);
}).interpolate("basis");
};
......
/* eslint-disable */
(function() {
window.ContributorsStatGraphUtil = {
parse_log: function(log) {
......
/* eslint-disable */
(function() {
this.GroupAvatar = (function() {
function GroupAvatar() {
......
/* eslint-disable */
(function() {
var slice = [].slice;
......
/* eslint-disable */
(function() {
this.ImporterStatus = (function() {
function ImporterStatus(jobs_url, import_url) {
......
/* eslint-disable */
(function() {
var issuable_created;
......
/* eslint-disable */
(function() {
this.IssuableContext = (function() {
function IssuableContext(currentUser) {
......
/* eslint-disable */
(function() {
var bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; };
......@@ -44,8 +45,8 @@
};
IssuableForm.prototype.handleSubmit = function() {
var ref, ref1;
if (((ref = parseInt((ref1 = this.issueMoveField) != null ? ref1.val() : void 0)) != null ? ref : 0) > 0) {
var fieldId = (this.issueMoveField != null) ? this.issueMoveField.val() : null;
if ((parseInt(fieldId) || 0) > 0) {
if (!confirm(this.issueMoveConfirmMsg)) {
return false;
}
......
/* eslint-disable */
/*= require flash */
/*= require jquery.waitforimages */
......
/* eslint-disable */
(function() {
this.IssueStatusSelect = (function() {
function IssueStatusSelect() {
......
/* eslint-disable */
((global) => {
class IssuableBulkActions {
......
/* eslint-disable */
((global) => {
class LabelManager {
......
/* eslint-disable */
(function() {
var bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; };
......
/* eslint-disable */
(function() {
this.LabelsSelect = (function() {
function LabelsSelect() {
......@@ -22,7 +23,7 @@
abilityName = $dropdown.data('ability-name');
$selectbox = $dropdown.closest('.selectbox');
$block = $selectbox.closest('.block');
$form = $dropdown.closest('form');
$form = $dropdown.closest('form, .js-issuable-update');
$sidebarCollapsedValue = $block.find('.sidebar-collapsed-icon span');
$sidebarLabelTooltip = $block.find('.js-sidebar-labels-tooltip');
$value = $block.find('.value');
......@@ -317,6 +318,7 @@
}
},
multiSelect: $dropdown.hasClass('js-multiselect'),
vue: $dropdown.hasClass('js-issue-board-sidebar'),
clicked: function(label, $el, e) {
var isIssueIndex, isMRIndex, page;
_this.enableBulkLabelDropdown();
......@@ -334,7 +336,7 @@
page = $('body').data('page');
isIssueIndex = page === 'projects:issues:index';
isMRIndex = page === 'projects:merge_requests:index';
if ($('html').hasClass('issue-boards-page')) {
if ($('html').hasClass('issue-boards-page') && !$dropdown.hasClass('js-issue-board-sidebar')) {
if (label.isAny) {
gl.issueBoards.BoardsStore.state.filters['label_name'] = [];
}
......@@ -362,6 +364,30 @@
else if ($dropdown.hasClass('js-filter-submit')) {
return $dropdown.closest('form').submit();
}
else if ($dropdown.hasClass('js-issue-board-sidebar')) {
if ($el.hasClass('is-active')) {
gl.issueBoards.BoardsStore.detail.issue.labels.push(new ListLabel({
id: label.id,
title: label.title,
color: label.color[0],
textColor: '#fff'
}));
}
else {
var labels = gl.issueBoards.BoardsStore.detail.issue.labels;
labels = labels.filter(function (selectedLabel) {
return selectedLabel.id !== label.id;
});
gl.issueBoards.BoardsStore.detail.issue.labels = labels;
}
$loading.fadeIn();
gl.issueBoards.BoardsStore.detail.issue.update($dropdown.attr('data-issue-update'))
.then(function () {
$loading.fadeOut();
});
}
else {
if ($dropdown.hasClass('js-multiselect')) {
......
/* eslint-disable */
(function() {
var hideEndFade;
......
/* eslint-disable */
/*= require ace-rails-ap */
/*= require ace/ext-searchbox */
/* eslint-disable */
/*= require Chart */
......
/* eslint-disable */
/*= require cropper */
......
/* eslint-disable */
/*= require d3 */
......
/* eslint-disable */
/*= require raphael */
/*= require g.raphael */
......
/* eslint-disable */
(function() {
(function(w) {
if (w.gl == null) {
......
/* eslint-disable */
(function() {
(function(w) {
var base;
......
/* eslint-disable */
(function() {
(function(w) {
var base;
......
/* eslint-disable */
/**
* Timeago is a jQuery plugin that makes it easy to support automatically
* updating fuzzy timestamps (e.g. "4 minutes ago" or "about 1 day ago").
......
/* eslint-disable */
(function() {
(function(w) {
var notificationGranted, notifyMe, notifyPermissions;
......
/* eslint-disable */
(function() {
(function(w) {
var base;
......
/* eslint-disable */
(function() {
(function(w) {
var base;
......
/* eslint-disable */
(function() {
(function(w) {
var base;
......
/* eslint-disable */
// LineHighlighter
//
// Handles single- and multi-line selection and highlight for blob views.
......
/* eslint-disable */
(function() {
Turbolinks.enableProgressBar();
......
/* eslint-disable */
(function() {
// Add datepickers to all `js-access-expiration-date` elements. If those elements are
// children of an element with the `clearable-input` class, and have a sibling
......
/* eslint-disable */
((w) => {
w.gl = w.gl || {};
......
/* eslint-disable */
((global) => {
global.mergeConflicts = global.mergeConflicts || {};
......
/* eslint-disable */
((global) => {
global.mergeConflicts = global.mergeConflicts || {};
......
/* eslint-disable */
((global) => {
global.mergeConflicts = global.mergeConflicts || {};
......
/* eslint-disable */
((global) => {
global.mergeConflicts = global.mergeConflicts || {};
......
/* eslint-disable */
((global) => {
global.mergeConflicts = global.mergeConflicts || {};
......
/* eslint-disable */
((global) => {
global.mergeConflicts = global.mergeConflicts || {};
const diffViewType = $.cookie('diff_view');
const diffViewType = Cookies.get('diff_view');
const HEAD_HEADER_TEXT = 'HEAD//our changes';
const ORIGIN_HEADER_TEXT = 'origin//their changes';
const HEAD_BUTTON_TITLE = 'Use ours';
......@@ -180,9 +181,7 @@
this.state.diffView = viewType;
this.state.isParallel = viewType === VIEW_TYPES.PARALLEL;
$.cookie('diff_view', viewType, {
path: gon.relative_url_root || '/'
});
Cookies.set('diff_view', viewType);
},
getHeadHeaderLine(id) {
......
/* eslint-disable */
//= require vue
//= require ./merge_conflict_store
//= require ./merge_conflict_service
......
/* eslint-disable */
((global) => {
global.mergeConflicts = global.mergeConflicts || {};
......
/* eslint-disable */
((global) => {
global.mergeConflicts = global.mergeConflicts || {};
......
/* eslint-disable */
/*= require jquery.waitforimages */
/*= require task_list */
......
/* eslint-disable */
// MergeRequestTabs
//
// Handles persisting and restoring the current tab selection and lazily-loading
// content on the MergeRequests#show page.
//
/*= require jquery.cookie */
/*= require js.cookie */
//
// ### Example Markup
......@@ -368,7 +369,7 @@
MergeRequestTabs.prototype.expandView = function() {
var $gutterIcon;
if ($.cookie('collapsed_gutter') === 'true') {
if (Cookies.get('collapsed_gutter') === 'true') {
return;
}
$gutterIcon = $('.js-sidebar-toggle i:visible');
......
/* eslint-disable */
((global) => {
var indexOf = [].indexOf || function(item) { for (var i = 0, l = this.length; i < l; i++) { if (i in this && this[i] === item) return i; } return -1; };
......
/* eslint-disable */
(function() {
var bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; };
......
/* eslint-disable */
(function() {
this.Milestone = (function() {
Milestone.updateIssue = function(li, issue_url, data) {
......
/* eslint-disable */
(function() {
this.MilestoneSelect = (function() {
function MilestoneSelect(currentProject) {
......@@ -101,6 +102,7 @@
// display:block overrides the hide-collapse rule
return $value.css('display', '');
},
vue: $dropdown.hasClass('js-issue-board-sidebar'),
clicked: function(selected, $el, e) {
var data, isIssueIndex, isMRIndex, page;
page = $('body').data('page');
......@@ -110,7 +112,7 @@
e.preventDefault();
return;
}
if ($('html').hasClass('issue-boards-page')) {
if ($('html').hasClass('issue-boards-page') && !$dropdown.hasClass('js-issue-board-sidebar')) {
gl.issueBoards.BoardsStore.state.filters[$dropdown.data('field-name')] = selected.name;
gl.issueBoards.BoardsStore.updateFiltersUrl();
e.preventDefault();
......@@ -123,6 +125,24 @@
return Issuable.filterResults($dropdown.closest('form'));
} else if ($dropdown.hasClass('js-filter-submit')) {
return $dropdown.closest('form').submit();
} else if ($dropdown.hasClass('js-issue-board-sidebar')) {
if (selected.id !== -1) {
Vue.set(gl.issueBoards.BoardsStore.detail.issue, 'milestone', new ListMilestone({
id: selected.id,
title: selected.name
}));
} else {
Vue.delete(gl.issueBoards.BoardsStore.detail.issue, 'milestone');
}
$dropdown.trigger('loading.gl.dropdown');
$loading.fadeIn();
gl.issueBoards.BoardsStore.detail.issue.update($dropdown.attr('data-issue-update'))
.then(function () {
$dropdown.trigger('loaded.gl.dropdown');
$loading.fadeOut();
});
} else {
selected = $selectbox.find('input[type="hidden"]').val();
data = {};
......
/* eslint-disable */
(function() {
var bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; };
......
/* eslint-disable */
(function() {
var bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; };
......
/* eslint-disable */
(function() {
this.Network = (function() {
function Network(opts) {
......
/* eslint-disable */
// This is a manifest file that'll be compiled into including all the files listed below.
// Add new JavaScript/Coffee code in separate files in this directory and they'll automatically
// be included in the compiled file accessible from http://example.com/assets/application.js
......
/* eslint-disable */
(function() {
var bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; },
indexOf = [].indexOf || function(item) { for (var i = 0, l = this.length; i < l; i++) { if (i in this && this[i] === item) return i; } return -1; };
......
/* eslint-disable */
(function() {
var bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; };
......
/* eslint-disable */
/*= require autosave */
/*= require autosize */
......
/* eslint-disable */
(function() {
this.NotificationsDropdown = (function() {
function NotificationsDropdown() {
......
/* eslint-disable */
(function() {
var bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; };
......
/* eslint-disable */
(function() {
this.Pager = {
init: function(limit, preload, disable, callback) {
......
/* eslint-disable */
((global) => {
class Pipelines {
......
/* eslint-disable */
// MarkdownPreview
//
// Handles toggling the "Write" and "Preview" tab clicks, rendering the preview,
......
/* eslint-disable */
((global) => {
// Matches everything but the file name
......
/* eslint-disable */
((global) => {
class Profile {
......
/* eslint-disable */
/*= require_tree . */
......
/* eslint-disable */
(function() {
this.Project = (function() {
function Project() {
......@@ -23,16 +24,12 @@
return $(this).parents('form').submit();
});
$('.hide-no-ssh-message').on('click', function(e) {
$.cookie('hide_no_ssh_message', 'false', {
path: gon.relative_url_root || '/'
});
Cookies.set('hide_no_ssh_message', 'false');
$(this).parents('.no-ssh-key-message').remove();
return e.preventDefault();
});
$('.hide-no-password-message').on('click', function(e) {
$.cookie('hide_no_password_message', 'false', {
path: gon.relative_url_root || '/'
});
Cookies.set('hide_no_password_message', 'false');
$(this).parents('.no-password-message').remove();
return e.preventDefault();
});
......@@ -82,7 +79,7 @@
if (ref.header != null) {
return $('<li />').addClass('dropdown-header').text(ref.header);
} else {
link = $('<a />').attr('href', '#').addClass(ref === selected ? 'is-active' : '').text(ref).attr('data-ref', escape(ref));
link = $('<a />').attr('href', '#').addClass(ref === selected ? 'is-active' : '').text(ref).attr('data-ref', ref);
return $('<li />').append(link);
}
},
......
/* eslint-disable */
(function() {
this.ProjectAvatar = (function() {
function ProjectAvatar() {
......
/* eslint-disable */
(function() {
var bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; };
......
/* eslint-disable */
(function() {
this.ProjectFork = (function() {
function ProjectFork() {
......
/* eslint-disable */
(function() {
this.ProjectImport = (function() {
function ProjectImport() {
......
/* eslint-disable */
(function() {
var bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; };
......
/* eslint-disable */
(function() {
this.ProjectSelect = (function() {
function ProjectSelect() {
......
/* eslint-disable */
(function() {
this.ProjectShow = (function() {
function ProjectShow() {}
......
/* eslint-disable */
(function() {
this.ProjectsList = {
init: function() {
......
/* eslint-disable */
(global => {
global.gl = global.gl || {};
......
/* eslint-disable */
(global => {
global.gl = global.gl || {};
......
/* eslint-disable */
class ProtectedBranchDropdown {
constructor(options) {
this.onSelect = options.onSelect;
......
/* eslint-disable */
(global => {
global.gl = global.gl || {};
......
/* eslint-disable */
(global => {
global.gl = global.gl || {};
......
/* eslint-disable */
(function() {
var bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; };
......@@ -5,15 +6,24 @@
function Sidebar(currentUser) {
this.toggleTodo = bind(this.toggleTodo, this);
this.sidebar = $('aside');
this.removeListeners();
this.addEventListeners();
}
Sidebar.prototype.removeListeners = function () {
this.sidebar.off('click', '.sidebar-collapsed-icon');
$('.dropdown').off('hidden.gl.dropdown');
$('.dropdown').off('loading.gl.dropdown');
$('.dropdown').off('loaded.gl.dropdown');
$(document).off('click', '.js-sidebar-toggle');
}
Sidebar.prototype.addEventListeners = function() {
this.sidebar.on('click', '.sidebar-collapsed-icon', this, this.sidebarCollapseClicked);
$('.dropdown').on('hidden.gl.dropdown', this, this.onSidebarDropdownHidden);
$('.dropdown').on('loading.gl.dropdown', this.sidebarDropdownLoading);
$('.dropdown').on('loaded.gl.dropdown', this.sidebarDropdownLoaded);
$(document).off('click', '.js-sidebar-toggle').on('click', '.js-sidebar-toggle', function(e, triggered) {
$(document).on('click', '.js-sidebar-toggle', function(e, triggered) {
var $allGutterToggleIcons, $this, $thisIcon;
e.preventDefault();
$this = $(this);
......@@ -29,9 +39,7 @@
$('.page-with-sidebar').removeClass('right-sidebar-collapsed').addClass('right-sidebar-expanded');
}
if (!triggered) {
return $.cookie("collapsed_gutter", $('.right-sidebar').hasClass('right-sidebar-collapsed'), {
path: gon.relative_url_root || '/'
});
return Cookies.set("collapsed_gutter", $('.right-sidebar').hasClass('right-sidebar-collapsed'));
}
});
return $(document).off('click', '.js-issuable-todo').on('click', '.js-issuable-todo', this.toggleTodo);
......
/* eslint-disable */
(function() {
this.Search = (function() {
function Search() {
......
/* eslint-disable */
((global) => {
const KEYCODE = {
......
/* eslint-disable */
(function() {
var bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; };
......
/* eslint-disable */
/*= require shortcuts */
......
/* eslint-disable */
/*= require shortcuts */
......
/* eslint-disable */
/*= require shortcuts_navigation */
......
/* eslint-disable */
/*= require mousetrap */
/*= require shortcuts_navigation */
......
/* eslint-disable */
/*= require shortcuts */
......
/* eslint-disable */
/*= require shortcuts_navigation */
......
/* eslint-disable */
((global) => {
let singleton;
......@@ -28,7 +29,7 @@
}
init() {
this.isPinned = $.cookie(pinnedStateCookie) === 'true';
this.isPinned = Cookies.get(pinnedStateCookie) === 'true';
this.isExpanded = (
window.innerWidth >= sidebarBreakpoint &&
$(pageSelector).hasClass(expandedPageClass)
......@@ -62,10 +63,7 @@
if (!this.isPinned) {
this.isExpanded = false;
}
$.cookie(pinnedStateCookie, this.isPinned ? 'true' : 'false', {
path: gon.relative_url_root || '/',
expires: 3650
});
Cookies.set(pinnedStateCookie, this.isPinned ? 'true' : 'false', { expires: 3650 });
this.renderState();
}
......
/* eslint-disable */
(function() {
var bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; };
......
/* eslint-disable */
/*= require_tree . */
(function() {
......
/* eslint-disable */
(global => {
global.gl = global.gl || {};
......
/* eslint-disable */
(function() {
this.Star = (function() {
function Star() {
......
/* eslint-disable */
(function() {
var bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; };
......@@ -5,10 +6,10 @@
function Subscription(container) {
this.toggleSubscription = bind(this.toggleSubscription, this);
var $container;
$container = $(container);
this.url = $container.attr('data-url');
this.subscribe_button = $container.find('.js-subscribe-button');
this.subscription_status = $container.find('.subscription-status');
this.$container = $(container);
this.url = this.$container.attr('data-url');
this.subscribe_button = this.$container.find('.js-subscribe-button');
this.subscription_status = this.$container.find('.subscription-status');
this.subscribe_button.unbind('click').click(this.toggleSubscription);
}
......@@ -18,10 +19,19 @@
action = btn.find('span').text();
current_status = this.subscription_status.attr('data-status');
btn.addClass('disabled');
if ($('html').hasClass('issue-boards-page')) {
this.url = this.$container.attr('data-url');
}
return $.post(this.url, (function(_this) {
return function() {
var status;
btn.removeClass('disabled');
if ($('html').hasClass('issue-boards-page')) {
Vue.set(gl.issueBoards.BoardsStore.detail.issue, 'subscribed', !gl.issueBoards.BoardsStore.detail.issue.subscribed);
} else {
status = current_status === 'subscribed' ? 'unsubscribed' : 'subscribed';
_this.subscription_status.attr('data-status', status);
action = status === 'subscribed' ? 'Unsubscribe' : 'Subscribe';
......@@ -30,6 +40,7 @@
if (btn.attr('data-original-title')) {
return btn.tooltip('hide').attr('data-original-title', action).tooltip('fixTitle');
}
}
};
})(this));
};
......
/* eslint-disable */
(function() {
this.SubscriptionSelect = (function() {
function SubscriptionSelect() {
......
/* eslint-disable */
// Syntax Highlighter
//
// Applies a syntax highlighting color scheme CSS class to any element with the
......
/* eslint-disable */
/*= require ../blob/template_selector */
((global) => {
......
/* eslint-disable */
((global) => {
class IssuableTemplateSelectors {
constructor({ $dropdowns, editor } = {}) {
......
/* eslint-disable */
((global) => {
class Todos {
......
/* eslint-disable */
(function() {
this.TreeView = (function() {
function TreeView() {
......
/* eslint-disable */
// Authenticate U2F (universal 2nd factor) devices for users to authenticate with.
//
// State Flow #1: setup -> in_progress -> authenticated -> POST to server
......
/* eslint-disable */
(function() {
var bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; };
......
/* eslint-disable */
// Register U2F (universal 2nd factor) devices for users to authenticate with.
//
// State Flow #1: setup -> in_progress -> registered -> POST to server
......
/* eslint-disable */
(function() {
this.U2FUtil = (function() {
function U2FUtil() {}
......
/* eslint-disable */
((global) => {
global.User = class {
constructor({ action }) {
......@@ -23,10 +24,7 @@
hideProjectLimitMessage() {
$('.hide-project-limit-message').on('click', e => {
e.preventDefault();
const path = gon.relative_url_root || '/';
$.cookie('hide_project_limit_message', 'false', {
path: path
});
Cookies.set('hide_project_limit_message', 'false');
$(this).parents('.project-limit-message').remove();
});
}
......
/* eslint-disable */
((global) => {
const debounceTimeoutDuration = 1000;
const invalidInputClass = 'gl-field-error-outline';
......
/* eslint-disable */
(function() {
var bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; };
......
/* eslint-disable */
/*= require_tree . */
......
/* eslint-disable */
(function() {
var bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; },
slice = [].slice;
......@@ -9,8 +10,12 @@
this.usersPath = "/autocomplete/users.json";
this.userPath = "/autocomplete/users/:id.json";
if (currentUser != null) {
if (typeof currentUser === 'object') {
this.currentUser = currentUser;
} else {
this.currentUser = JSON.parse(currentUser);
}
}
$('.js-user-search').each((function(_this) {
return function(i, dropdown) {
var options = {};
......@@ -32,9 +37,30 @@
$value = $block.find('.value');
$collapsedSidebar = $block.find('.sidebar-collapsed-user');
$loading = $block.find('.block-loading').fadeOut();
var updateIssueBoardsIssue = function () {
$loading.fadeIn();
gl.issueBoards.BoardsStore.detail.issue.update($dropdown.attr('data-issue-update'))
.then(function () {
$loading.fadeOut();
});
};
$block.on('click', '.js-assign-yourself', function(e) {
e.preventDefault();
if ($dropdown.hasClass('js-issue-board-sidebar')) {
Vue.set(gl.issueBoards.BoardsStore.detail.issue, 'assignee', new ListUser({
id: _this.currentUser.id,
username: _this.currentUser.username,
name: _this.currentUser.name,
avatar_url: _this.currentUser.avatar_url
}));
updateIssueBoardsIssue();
} else {
return assignTo(_this.currentUser.id);
}
});
assignTo = function(selected) {
var data;
......@@ -150,6 +176,7 @@
// display:block overrides the hide-collapse rule
return $value.css('display', '');
},
vue: $dropdown.hasClass('js-issue-board-sidebar'),
clicked: function(user, $el, e) {
var isIssueIndex, isMRIndex, page, selected;
page = $('body').data('page');
......@@ -160,7 +187,7 @@
selectedId = user.id;
return;
}
if ($('html').hasClass('issue-boards-page')) {
if ($('html').hasClass('issue-boards-page') && !$dropdown.hasClass('js-issue-board-sidebar')) {
selectedId = user.id;
gl.issueBoards.BoardsStore.state.filters[$dropdown.data('field-name')] = user.id;
gl.issueBoards.BoardsStore.updateFiltersUrl();
......@@ -170,6 +197,19 @@
return Issuable.filterResults($dropdown.closest('form'));
} else if ($dropdown.hasClass('js-filter-submit')) {
return $dropdown.closest('form').submit();
} else if ($dropdown.hasClass('js-issue-board-sidebar')) {
if (user.id) {
Vue.set(gl.issueBoards.BoardsStore.detail.issue, 'assignee', new ListUser({
id: user.id,
username: user.username,
name: user.name,
avatar_url: user.avatar_url
}));
} else {
Vue.delete(gl.issueBoards.BoardsStore.detail.issue, 'assignee');
}
updateIssueBoardsIssue();
} else {
selected = $dropdown.closest('.selectbox').find("input[name='" + ($dropdown.data('field-name')) + "']").val();
return assignTo(selected);
......
/* eslint-disable */
/*= require latinise */
......
/* eslint-disable */
// Zen Mode (full screen) textarea
//
/*= provides zen_mode:enter */
......
......@@ -227,7 +227,7 @@ header {
float: none !important;
.visible-xs,
.visable-sm {
.visible-sm {
display: table-cell !important;
}
}
......
......@@ -45,6 +45,15 @@
.page-with-sidebar {
padding-bottom: 0;
}
.issues-filters {
position: relative;
z-index: 999999;
}
}
.boards-app {
position: relative;
}
.boards-app-loading {
......@@ -66,6 +75,10 @@
height: 475px; // Needed for PhantomJS
height: calc(100vh - 220px);
min-height: 475px;
&.is-compact {
width: calc(100% - 290px);
}
}
}
......@@ -184,6 +197,10 @@
margin-bottom: 5px;
}
&.is-active {
background-color: $row-hover;
}
.label {
border: 0;
outline: 0;
......@@ -212,6 +229,10 @@
margin-right: 5px;
font-size: (14px / $issue-boards-font-size) * 1em;
}
.avatar {
margin-left: 0;
}
}
.card-number {
......@@ -264,3 +285,48 @@
border-width: 1px 0 1px 1px;
}
}
.issue-boards-sidebar {
&.right-sidebar {
top: 153px;
bottom: 0;
@media (min-width: $screen-sm-min) {
top: 220px;
}
}
.issuable-sidebar-header {
position: relative;
}
.gutter-toggle {
position: absolute;
top: 0;
bottom: 15px;
right: 0;
width: 22px;
color: $gray-darkest;
svg {
position: absolute;
top: 50%;
margin-top: (-11px / 2);
}
&:hover {
path {
fill: $gray-darkest;
}
}
}
.issuable-header-text {
width: 100%;
padding-right: 35px;
> strong {
font-weight: 600;
}
}
}
......@@ -73,10 +73,13 @@ module Projects
def serialize_as_json(resource)
resource.as_json(
labels: true,
only: [:iid, :title, :confidential],
only: [:iid, :title, :confidential, :due_date],
include: {
assignee: { only: [:id, :name, :username], methods: [:avatar_url] }
})
assignee: { only: [:id, :name, :username], methods: [:avatar_url] },
milestone: { only: [:id, :title] }
},
user: current_user
)
end
end
end
......
......@@ -5,7 +5,7 @@ module SidekiqHelper
(?<mem>[\d\.,]+)\s+
(?<state>[DRSTWXZNLsl\+<]+)\s+
(?<start>.+)\s+
(?<command>sidekiq.*\])\s+
(?<command>sidekiq.*\])\s*
\z/x
def parse_sidekiq_ps(line)
......
......@@ -287,10 +287,12 @@ class Issue < ActiveRecord::Base
def as_json(options = {})
super(options).tap do |json|
json[:subscribed] = subscribed?(options[:user]) if options.has_key?(:user)
if options.has_key?(:labels)
json[:labels] = labels.as_json(
project: project,
only: [:id, :title, :description, :color],
only: [:id, :title, :description, :color, :priority],
methods: [:text_color]
)
end
......
......@@ -461,7 +461,7 @@
.panel-body
= lorem
%h2#alert Alerts
%h2#alerts Alerts
.row
.col-md-6
......
......@@ -7,8 +7,11 @@
":issue-link-base" => "issueLinkBase",
":disabled" => "disabled",
"track-by" => "id" }
%li.card{ ":class" => "{ 'user-can-drag': !disabled && issue.id, 'is-disabled': disabled || !issue.id }",
":index" => "index" }
%li.card{ ":class" => "{ 'user-can-drag': !disabled && issue.id, 'is-disabled': disabled || !issue.id, 'is-active': issueDetailVisible }",
":index" => "index",
"@mousedown" => "mouseDown",
"@mouseMove" => "mouseMove",
"@mouseup" => "showIssue($event)" }
%h4.card-title
= icon("eye-slash", class: "confidential-icon", "v-if" => "issue.confidential")
%a{ ":href" => "issueLinkBase + '/' + issue.id",
......@@ -18,6 +21,11 @@
%span.card-number{ "v-if" => "issue.id" }
= precede '#' do
{{ issue.id }}
%a.has-tooltip{ ":href" => "'#{root_path}' + issue.assignee.username",
":title" => "'Assigned to ' + issue.assignee.name",
"v-if" => "issue.assignee",
data: { container: 'body' } }
%img.avatar.avatar-inline.s20{ ":src" => "issue.assignee.avatar", width: 20, height: 20 }
%button.label.color-label.has-tooltip{ "v-for" => "label in issue.labels",
type: "button",
"v-if" => "(!list.label || label.id !== list.label.id)",
......@@ -26,8 +34,3 @@
":title" => "label.description",
data: { container: 'body' } }
{{ label.title }}
%a.has-tooltip{ ":href" => "'#{root_path}' + issue.assignee.username",
":title" => "'Assigned to ' + issue.assignee.name",
"v-if" => "issue.assignee",
data: { container: 'body' } }
%img.avatar.avatar-inline.s20{ ":src" => "issue.assignee.avatar", width: 20, height: 20 }
%board-sidebar{ "inline-template" => true,
":current-user" => "#{current_user.to_json(only: [:username, :id, :name], methods: [:avatar_url]) if current_user}" }
%aside.right-sidebar.right-sidebar-expanded.issue-boards-sidebar{ "v-show" => "showSidebar" }
.issuable-sidebar
.block.issuable-sidebar-header
%span.issuable-header-text.hide-collapsed.pull-left
%strong
{{ issue.title }}
%br/
%span
= precede "#" do
{{ issue.id }}
%a.gutter-toggle.pull-right{ role: "button",
href: "#",
"@click.prevent" => "closeSidebar",
"aria-label" => "Toggle sidebar" }
= custom_icon("icon_close", size: 15)
.js-issuable-update
= render "projects/boards/components/sidebar/assignee"
= render "projects/boards/components/sidebar/milestone"
= render "projects/boards/components/sidebar/due_date"
= render "projects/boards/components/sidebar/labels"
= render "projects/boards/components/sidebar/notifications"
.block.assignee
.title.hide-collapsed
Assignee
= icon("spinner spin", class: "block-loading")
- if can?(current_user, :admin_issue, @project)
= link_to "Edit", "#", class: "edit-link pull-right"
.value.hide-collapsed
%span.assign-yourself.no-value{ "v-if" => "!issue.assignee" }
No assignee
- if can?(current_user, :admin_issue, @project)
\-
%a.js-assign-yourself{ href: "#" }
assign yourself
%a.author_link.bold{ ":href" => "'#{root_url}' + issue.assignee.username",
"v-if" => "issue.assignee" }
%img.avatar.avatar-inline.s32{ ":src" => "issue.assignee.avatar",
width: "32" }
%span.author
{{ issue.assignee.name }}
%span.username
= precede "@" do
{{ issue.assignee.username }}
- if can?(current_user, :admin_issue, @project)
.selectbox.hide-collapsed
%input{ type: "hidden",
name: "issue[assignee_id]",
id: "issue_assignee_id",
":value" => "issue.assignee.id",
"v-if" => "issue.assignee" }
.dropdown
%button.dropdown-menu-toggle.js-user-search.js-author-search.js-issue-board-sidebar{ type: "button", data: { toggle: "dropdown", field_name: "issue[assignee_id]", first_user: (current_user.username if current_user), current_user: "true", project_id: @project.id, null_user: "true" },
":data-issuable-id" => "issue.id",
":data-issue-update" => "'#{namespace_project_issues_path(@project.namespace, @project)}/' + issue.id + '.json'" }
Select assignee
= icon("chevron-down")
.dropdown-menu.dropdown-menu-user.dropdown-menu-selectable.dropdown-menu-author
= dropdown_title("Assign to")
= dropdown_filter("Search users")
= dropdown_content
= dropdown_loading
.block.due_date
.title
Due date
= icon("spinner spin", class: "block-loading")
- if can?(current_user, :admin_issue, @project)
= link_to "Edit", "#", class: "edit-link pull-right"
.value
.value-content
%span.no-value{ "v-if" => "!issue.dueDate" }
No due date
%span.bold{ "v-if" => "issue.dueDate" }
{{ issue.dueDate | due-date }}
- if can?(current_user, :admin_issue, @project)
%span.no-value.js-remove-due-date-holder{ "v-if" => "issue.dueDate" }
\-
%a.js-remove-due-date{ href: "#", role: "button" }
remove due date
- if can?(current_user, :admin_issue, @project)
.selectbox
%input{ type: "hidden",
name: "issue[due_date]",
":value" => "issue.dueDate" }
.dropdown
%button.dropdown-menu-toggle.js-due-date-select.js-issue-boards-due-date{ type: 'button',
data: { toggle: 'dropdown', field_name: "issue[due_date]", ability_name: "issue" },
":data-issue-update" => "'#{namespace_project_issues_path(@project.namespace, @project)}/' + issue.id + '.json'" }
%span.dropdown-toggle-text Due date
= icon('chevron-down')
.dropdown-menu.dropdown-menu-due-date
= dropdown_title('Due date')
= dropdown_content do
.js-due-date-calendar
.block.labels
.title
Labels
= icon("spinner spin", class: "block-loading")
- if can?(current_user, :admin_issue, @project)
= link_to "Edit", "#", class: "edit-link pull-right"
.value.issuable-show-labels
%span.no-value{ "v-if" => "issue.labels && issue.labels.length === 0" }
None
%a{ href: "#",
"v-for" => "label in issue.labels" }
%span.label.color-label.has-tooltip{ ":style" => "{ backgroundColor: label.color, color: label.textColor }" }
{{ label.title }}
- if can?(current_user, :admin_issue, @project)
.selectbox
%input{ type: "hidden",
name: "issue[label_names][]",
"v-for" => "label in issue.labels",
":value" => "label.id" }
.dropdown
%button.dropdown-menu-toggle.js-label-select.js-multiselect.js-issue-board-sidebar{ type: "button",
data: { toggle: "dropdown", field_name: "issue[label_names][]", show_no: "true", show_any: "true", project_id: @project.id, labels: namespace_project_labels_path(@project.namespace, @project, :json), namespace_path: @project.try(:namespace).try(:path), project_path: @project.try(:path) },
":data-issue-update" => "'#{namespace_project_issues_path(@project.namespace, @project)}/' + issue.id + '.json'" }
%span.dropdown-toggle-text
Label
= icon('chevron-down')
.dropdown-menu.dropdown-select.dropdown-menu-paging.dropdown-menu-labels.dropdown-menu-selectable
= render partial: "shared/issuable/label_page_default"
- if can? current_user, :admin_label, @project and @project
= render partial: "shared/issuable/label_page_create"
.block.milestone
.title
Milestone
= icon("spinner spin", class: "block-loading")
- if can?(current_user, :admin_issue, @project)
= link_to "Edit", "#", class: "edit-link pull-right"
.value
%span.no-value{ "v-if" => "!issue.milestone" }
None
%span.bold.has-tooltip{ "v-if" => "issue.milestone" }
{{ issue.milestone.title }}
- if can?(current_user, :admin_issue, @project)
.selectbox
%input{ type: "hidden",
":value" => "issue.milestone.id",
name: "issue[milestone_id]",
"v-if" => "issue.milestone" }
.dropdown
%button.dropdown-menu-toggle.js-milestone-select.js-issue-board-sidebar{ type: "button", data: { toggle: "dropdown", show_no: "true", field_name: "issue[milestone_id]", project_id: @project.id, milestones: namespace_project_milestones_path(@project.namespace, @project, :json), ability_name: "issue", use_id: "true" },
":data-issuable-id" => "issue.id",
":data-issue-update" => "'#{namespace_project_issues_path(@project.namespace, @project)}/' + issue.id + '.json'" }
Milestone
= icon("chevron-down")
.dropdown-menu.dropdown-select.dropdown-menu-selectable
= dropdown_title("Assignee milestone")
= dropdown_filter("Search milestones")
= dropdown_content
= dropdown_loading
- if current_user
.block.light.subscription{ ":data-url" => "'#{namespace_project_issues_path(@project.namespace, @project)}/' + issue.id + '/toggle_subscription'" }
.title
Notifications
%button.btn.btn-block.btn-default.js-subscribe-button.issuable-subscribe-button.hide-collapsed{ type: "button" }
{{ issue.subscribed ? 'Unsubscribe' : 'Subscribe' }}
.subscription-status{ ":data-status" => "issue.subscribed ? 'subscribed' : 'unsubscribed'" }
.unsubscribed{ "v-show" => "!issue.subscribed" }
You're not receiving notifications from this thread.
.subscribed{ "v-show" => "issue.subscribed" }
You're receiving notifications because you're subscribed to this thread.
......@@ -10,7 +10,9 @@
= render 'shared/issuable/filter', type: :boards
.boards-list#board-app{ "v-cloak" => true, data: board_data }
#board-app.boards-app{ "v-cloak" => true, data: board_data }
.boards-list{ ":class" => "{ 'is-compact': detailIssueVisible }" }
.boards-app-loading.text-center{ "v-if" => "loading" }
= icon("spinner spin")
= render "projects/boards/components/board"
= render "projects/boards/components/sidebar"
......@@ -10,7 +10,9 @@
= render 'shared/issuable/filter', type: :boards
.boards-list#board-app{ "v-cloak" => true, data: board_data }
#board-app.boards-app{ "v-cloak" => true, data: board_data }
.boards-list{ ":class" => "{ 'is-compact': detailIssueVisible }" }
.boards-app-loading.text-center{ "v-if" => "loading" }
= icon("spinner spin")
= render "projects/boards/components/board"
= render "projects/boards/components/sidebar"
/ Side-by-side diff view
%div.text-file.diff-wrap-lines.code.file-content.js-syntax-highlight{ data: diff_view_data }
%div.text-file.diff-wrap-lines.code.js-syntax-highlight{ data: diff_view_data }
%table
- last_line = 0
- diff_file.parallel_diff_lines.each do |line|
......
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 15 15"><path d="M9,7.5l5.83-5.91a.48.48,0,0,0,0-.69L14.11.15a.46.46,0,0,0-.68,0l-5.93,6L1.57.15a.46.46,0,0,0-.68,0L.15.9a.48.48,0,0,0,0,.69L6,7.5.15,13.41a.48.48,0,0,0,0,.69l.74.75a.46.46,0,0,0,.68,0l5.93-6,5.93,6a.46.46,0,0,0,.68,0l.74-.75a.48.48,0,0,0,0-.69Z"/></svg>
\ No newline at end of file
......@@ -451,43 +451,49 @@ GET /projects/:id/services/irker
## JIRA
Jira issue tracker
JIRA issue tracker.
### Get JIRA service settings
Get JIRA service settings for a project.
```
GET /projects/:id/services/jira
```
### Create/Edit JIRA service
Set JIRA service for a project.
> Setting `project_url`, `issues_url` and `new_issue_url` will allow a user to easily navigate to the Jira issue tracker. See the [integration doc](http://docs.gitlab.com/ce/integration/external-issue-tracker.html) for details. Support for referencing commits and automatic closing of Jira issues directly from GitLab is [available in GitLab EE.](http://docs.gitlab.com/ee/integration/jira.html)
>**Note:**
Setting `project_url`, `issues_url` and `new_issue_url` will allow a user to
easily navigate to the JIRA issue tracker. See the [integration doc][jira-doc]
for details.
```
PUT /projects/:id/services/jira
```
Parameters:
- `new_issue_url` (**required**) - New Issue url
- `project_url` (**required**) - Project url
- `issues_url` (**required**) - Issue url
- `description` (optional) - Jira issue tracker
- `username` (optional) - Jira username
- `password` (optional) - Jira password
| Attribute | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `active` | boolean| no | Enable/disable the JIRA service. |
| `project_url` | string | yes | The URL to the JIRA project which is being linked to this GitLab project. It is of the form: `https://<jira_host_url>/issues/?jql=project=<jira_project>`. |
| `issues_url` | string | yes | The URL to the JIRA project issues overview for the project that is linked to this GitLab project. It is of the form: `https://<jira_host_url>/browse/:id`. Leave `:id` as-is, it gets replaced by GitLab at runtime.|
| `new_issue_url` | string | yes | 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://<jira_host_url>/secure/CreateIssue.jspa` |
| `api_url` | string | yes | 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://<jira_host_url>/rest/api/2`. |
| `description` | string | no | A name for the issue tracker. |
| `username` | string | no | The username of the user created to be used with GitLab/JIRA. |
| `password` | string | no | The password of the user created to be used with GitLab/JIRA. |
| `jira_issue_transition_id` | string | no | 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][trans]). By default, this ID is set to `2`. |
### Delete JIRA service
Delete JIRA service for a project.
Remove all previously JIRA settings from a project.
```
DELETE /projects/:id/services/jira
```
### Get JIRA service settings
Get JIRA service settings for a project.
```
GET /projects/:id/services/jira
```
## PivotalTracker
Project Management Software (Source Commits Endpoint)
......@@ -662,3 +668,5 @@ Get JetBrains TeamCity CI service settings for a project.
```
GET /projects/:id/services/teamcity
```
[jira-doc]: ../project_services/jira.md
......@@ -142,6 +142,9 @@ gitlab-workhorse we need a Go compiler. The instructions below assume you
use 64-bit Linux. You can find downloads for other platforms at the [Go download
page](https://golang.org/dl).
# Remove former Go installation folder
sudo rm -rf /usr/local/go
curl --remote-name --progress https://storage.googleapis.com/golang/go1.5.3.linux-amd64.tar.gz
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
......
......@@ -85,8 +85,11 @@ Deleting old backups... [SKIPPING]
Starting with GitLab 7.4 you can let the backup script upload the '.tar' file it creates.
It uses the [Fog library](http://fog.io/) to perform the upload.
In the example below we use Amazon S3 for storage.
Fog also supports [other storage providers](http://fog.io/storage/).
In the example below we use Amazon S3 for storage, but Fog also lets you use
[other storage providers](http://fog.io/storage/). GitLab
[imports cloud drivers](https://gitlab.com/gitlab-org/gitlab-ce/blob/30f5b9a5b711b46f1065baf755e413ceced5646b/Gemfile#L88)
for AWS, Azure, Google, OpenStack Swift and Rackspace as well. A local driver is
[also available](#uploading-to-locally-mounted-shares).
For omnibus packages:
......
......@@ -49,18 +49,23 @@ module API
attrs = attributes_for_keys [:title, :key]
attrs[:key].strip! if attrs[:key]
# Check for an existing key joined to this project
key = user_project.deploy_keys.find_by(key: attrs[:key])
present key, with: Entities::SSHKey if key
if key
present key, with: Entities::SSHKey
break
end
# Check for available deploy keys in other projects
key = current_user.accessible_deploy_keys.find_by(key: attrs[:key])
if key
user_project.deploy_keys << key
present key, with: Entities::SSHKey
break
end
# Create a new deploy key
key = DeployKey.new attrs
if key.valid? && user_project.deploy_keys << key
present key, with: Entities::SSHKey
else
......
......@@ -4,25 +4,24 @@ module API
before { authenticate! }
before { authorize! :download_code, user_project }
params do
requires :id, type: String, desc: 'The ID of a project'
end
resource :projects do
# Get a project repository tags
#
# Parameters:
# id (required) - The ID of a project
# Example Request:
# GET /projects/:id/repository/tags
desc 'Get a project repository tags' do
success Entities::RepoTag
end
get ":id/repository/tags" do
present user_project.repository.tags.sort_by(&:name).reverse,
with: Entities::RepoTag, project: user_project
end
# Get a single repository tag
#
# Parameters:
# id (required) - The ID of a project
# tag_name (required) - The name of the tag
# Example Request:
# GET /projects/:id/repository/tags/:tag_name
desc 'Get a single repository tag' do
success Entities::RepoTag
end
params do
requires :tag_name, type: String, desc: 'The name of the tag'
end
get ":id/repository/tags/:tag_name", requirements: { tag_name: /.+/ } do
tag = user_project.repository.find_tag(params[:tag_name])
not_found!('Tag') unless tag
......@@ -30,20 +29,21 @@ module API
present tag, with: Entities::RepoTag, project: user_project
end
# Create tag
#
# Parameters:
# id (required) - The ID of a project
# tag_name (required) - The name of the tag
# ref (required) - Create tag from commit sha or branch
# message (optional) - Specifying a message creates an annotated tag.
# Example Request:
# POST /projects/:id/repository/tags
desc 'Create a new repository tag' do
success Entities::RepoTag
end
params do
requires :tag_name, type: String, desc: 'The name of the tag'
requires :ref, type: String, desc: 'The commit sha or branch name'
optional :message, type: String, desc: 'Specifying a message creates an annotated tag'
optional :release_description, type: String, desc: 'Specifying release notes stored in the GitLab database'
end
post ':id/repository/tags' do
authorize_push_project
message = params[:message] || nil
create_params = declared(params)
result = CreateTagService.new(user_project, current_user).
execute(params[:tag_name], params[:ref], message, params[:release_description])
execute(create_params[:tag_name], create_params[:ref], create_params[:message], create_params[:release_description])
if result[:status] == :success
present result[:tag],
......@@ -54,15 +54,13 @@ module API
end
end
# Delete tag
#
# Parameters:
# id (required) - The ID of a project
# tag_name (required) - The name of the tag
# Example Request:
# DELETE /projects/:id/repository/tags/:tag
desc 'Delete a repository tag'
params do
requires :tag_name, type: String, desc: 'The name of the tag'
end
delete ":id/repository/tags/:tag_name", requirements: { tag_name: /.+/ } do
authorize_push_project
result = DeleteTagService.new(user_project, current_user).
execute(params[:tag_name])
......@@ -75,17 +73,16 @@ module API
end
end
# Add release notes to tag
#
# Parameters:
# id (required) - The ID of a project
# tag_name (required) - The name of the tag
# description (required) - Release notes with markdown support
# Example Request:
# POST /projects/:id/repository/tags/:tag_name/release
desc 'Add a release note to a tag' do
success Entities::Release
end
params do
requires :tag_name, type: String, desc: 'The name of the tag'
requires :description, type: String, desc: 'Release notes with markdown support'
end
post ':id/repository/tags/:tag_name/release', requirements: { tag_name: /.+/ } do
authorize_push_project
required_attributes! [:description]
result = CreateReleaseService.new(user_project, current_user).
execute(params[:tag_name], params[:description])
......@@ -96,17 +93,16 @@ module API
end
end
# Updates a release notes of a tag
#
# Parameters:
# id (required) - The ID of a project
# tag_name (required) - The name of the tag
# description (required) - Release notes with markdown support
# Example Request:
# PUT /projects/:id/repository/tags/:tag_name/release
desc "Update a tag's release note" do
success Entities::Release
end
params do
requires :tag_name, type: String, desc: 'The name of the tag'
requires :description, type: String, desc: 'Release notes with markdown support'
end
put ':id/repository/tags/:tag_name/release', requirements: { tag_name: /.+/ } do
authorize_push_project
required_attributes! [:description]
result = UpdateReleaseService.new(user_project, current_user).
execute(params[:tag_name], params[:description])
......
unless Rails.env.production?
desc "GitLab | Run ESLint"
task :eslint do
system("npm", "run", "eslint")
end
end
unless Rails.env.production?
namespace :lint do
desc "GitLab | lint | Lint JavaScript files using ESLint"
task :javascript do
Rake::Task['eslint'].invoke
end
end
end
{
"private": true,
"scripts": {
"eslint": "eslint .",
"eslint-fix": "eslint --fix ."
},
"devDependencies": {
"eslint": "^3.1.1",
"eslint-config-airbnb": "^12.0.0",
"eslint-plugin-import": "^2.0.1",
"eslint-plugin-jsx-a11y": "^2.2.3",
"eslint-plugin-react": "^6.4.1"
}
}
......@@ -21,9 +21,11 @@ describe Projects::Boards::IssuesController do
context 'with valid list id' do
it 'returns issues that have the list label applied' do
johndoe = create(:user, avatar: fixture_file_upload(File.join(Rails.root, 'spec/fixtures/dk.png')))
issue = create(:labeled_issue, project: project, labels: [planning])
create(:labeled_issue, project: project, labels: [planning])
create(:labeled_issue, project: project, labels: [development])
create(:labeled_issue, project: project, labels: [development], due_date: Date.tomorrow)
create(:labeled_issue, project: project, labels: [development], assignee: johndoe)
issue.subscribe(johndoe)
list_issues user: user, board: board, list: list2
......
......@@ -66,6 +66,21 @@ describe 'Issue Boards new issue', feature: true, js: true do
expect(page).to have_content('1')
end
end
it 'shows sidebar when creating new issue' do
page.within(first('.board')) do
find('.board-issue-count-holder .btn').click
end
page.within(first('.board-new-issue-form')) do
find('.form-control').set('bug')
click_button 'Submit issue'
end
wait_for_vue_resource
expect(page).to have_selector('.issue-boards-sidebar')
end
end
context 'unauthorized user' do
......
require 'rails_helper'
describe 'Issue Boards', feature: true, js: true do
include WaitForAjax
include WaitForVueResource
let(:project) { create(:empty_project, :public) }
let(:board) { create(:board, project: project) }
let(:user) { create(:user) }
let!(:label) { create(:label, project: project) }
let!(:label2) { create(:label, project: project) }
let!(:milestone) { create(:milestone, project: project) }
let!(:issue2) { create(:labeled_issue, project: project, assignee: user, milestone: milestone, labels: [label]) }
let!(:issue) { create(:issue, project: project) }
before do
project.team << [user, :master]
login_as(user)
visit namespace_project_board_path(project.namespace, project, board)
wait_for_vue_resource
end
it 'shows sidebar when clicking issue' do
page.within(first('.board')) do
first('.card').click
end
expect(page).to have_selector('.issue-boards-sidebar')
end
it 'closes sidebar when clicking issue' do
page.within(first('.board')) do
first('.card').click
end
expect(page).to have_selector('.issue-boards-sidebar')
page.within(first('.board')) do
first('.card').click
end
expect(page).not_to have_selector('.issue-boards-sidebar')
end
it 'closes sidebar when clicking close button' do
page.within(first('.board')) do
first('.card').click
end
expect(page).to have_selector('.issue-boards-sidebar')
find('.gutter-toggle').click
expect(page).not_to have_selector('.issue-boards-sidebar')
end
it 'shows issue details when sidebar is open' do
page.within(first('.board')) do
first('.card').click
end
page.within('.issue-boards-sidebar') do
expect(page).to have_content(issue.title)
expect(page).to have_content(issue.to_reference)
end
end
context 'assignee' do
it 'updates the issues assignee' do
page.within(first('.board')) do
first('.card').click
end
page.within('.assignee') do
click_link 'Edit'
wait_for_ajax
page.within('.dropdown-menu-user') do
click_link user.name
wait_for_vue_resource
end
expect(page).to have_content(user.name)
end
page.within(first('.board')) do
page.within(first('.card')) do
expect(page).to have_selector('.avatar')
end
end
end
it 'removes the assignee' do
page.within(first('.board')) do
find('.card:nth-child(2)').click
end
page.within('.assignee') do
click_link 'Edit'
wait_for_ajax
page.within('.dropdown-menu-user') do
click_link 'Unassigned'
wait_for_vue_resource
end
expect(page).to have_content('No assignee')
end
page.within(first('.board')) do
page.within(find('.card:nth-child(2)')) do
expect(page).not_to have_selector('.avatar')
end
end
end
it 'assignees to current user' do
page.within(first('.board')) do
first('.card').click
end
page.within('.assignee') do
click_link 'assign yourself'
wait_for_vue_resource
expect(page).to have_content(user.name)
end
page.within(first('.board')) do
page.within(first('.card')) do
expect(page).to have_selector('.avatar')
end
end
end
end
context 'milestone' do
it 'adds a milestone' do
page.within(first('.board')) do
first('.card').click
end
page.within('.milestone') do
click_link 'Edit'
wait_for_ajax
click_link milestone.title
wait_for_vue_resource
page.within('.value') do
expect(page).to have_content(milestone.title)
end
end
end
it 'removes a milestone' do
page.within(first('.board')) do
find('.card:nth-child(2)').click
end
page.within('.milestone') do
click_link 'Edit'
wait_for_ajax
click_link "No Milestone"
wait_for_vue_resource
page.within('.value') do
expect(page).not_to have_content(milestone.title)
end
end
end
end
context 'due date' do
it 'updates due date' do
page.within(first('.board')) do
first('.card').click
end
page.within('.due_date') do
click_link 'Edit'
click_link Date.today.day
wait_for_vue_resource
expect(page).to have_content(Date.today.to_s(:medium))
end
end
end
context 'labels' do
it 'adds a single label' do
page.within(first('.board')) do
first('.card').click
end
page.within('.labels') do
click_link 'Edit'
wait_for_ajax
click_link label.title
wait_for_vue_resource
find('.dropdown-menu-close-icon').click
page.within('.value') do
expect(page).to have_selector('.label', count: 1)
expect(page).to have_content(label.title)
end
end
page.within(first('.board')) do
page.within(first('.card')) do
expect(page).to have_selector('.label', count: 1)
expect(page).to have_content(label.title)
end
end
end
it 'adds a multiple labels' do
page.within(first('.board')) do
first('.card').click
end
page.within('.labels') do
click_link 'Edit'
wait_for_ajax
click_link label.title
click_link label2.title
wait_for_vue_resource
find('.dropdown-menu-close-icon').click
page.within('.value') do
expect(page).to have_selector('.label', count: 2)
expect(page).to have_content(label.title)
expect(page).to have_content(label2.title)
end
end
page.within(first('.board')) do
page.within(first('.card')) do
expect(page).to have_selector('.label', count: 2)
expect(page).to have_content(label.title)
expect(page).to have_content(label2.title)
end
end
end
it 'removes a label' do
page.within(first('.board')) do
find('.card:nth-child(2)').click
end
page.within('.labels') do
click_link 'Edit'
wait_for_ajax
click_link label.title
wait_for_vue_resource
find('.dropdown-menu-close-icon').click
page.within('.value') do
expect(page).to have_selector('.label', count: 0)
expect(page).not_to have_content(label.title)
end
end
page.within(first('.board')) do
page.within(find('.card:nth-child(2)')) do
expect(page).not_to have_selector('.label', count: 1)
expect(page).not_to have_content(label.title)
end
end
end
end
context 'subscription' do
it 'changes issue subscription' do
page.within(first('.board')) do
first('.card').click
end
page.within('.subscription') do
click_button 'Subscribe'
expect(page).to have_content("You're receiving notifications because you're subscribed to this thread.")
end
end
end
end
......@@ -58,6 +58,22 @@ feature 'Issue filtering by Milestone', feature: true do
expect(page).to have_css('.issue', count: 1)
end
context 'when milestone has single quotes in title' do
background do
milestone.update(name: "rock 'n' roll")
end
scenario 'filters by a specific Milestone', js: true do
create(:issue, project: project, milestone: milestone)
create(:issue, project: project)
visit_issues(project)
filter_by_milestone(milestone.title)
expect(page).to have_css('.issue', count: 1)
end
end
def visit_issues(project)
visit namespace_project_issues_path(project.namespace, project)
end
......
......@@ -40,8 +40,6 @@ feature 'Merge request created from fork' do
end
context 'pipeline present in source project' do
include WaitForAjax
given(:pipeline) do
create(:ci_pipeline,
project: fork_project,
......@@ -57,7 +55,6 @@ feature 'Merge request created from fork' do
scenario 'user visits a pipelines page', js: true do
visit_merge_request(merge_request)
page.within('.merge-request-tabs') { click_link 'Builds' }
wait_for_ajax
page.within('table.ci-table') do
expect(page).to have_content 'rspec'
......
......@@ -67,6 +67,23 @@ feature 'Merge Request filtering by Milestone', feature: true do
expect(page).to have_css('.merge-request', count: 1)
end
context 'when milestone has single quotes in title' do
background do
milestone.update(name: "rock 'n' roll")
end
scenario 'filters by a specific Milestone', js: true do
create(:merge_request, :with_diffs, source_project: project, milestone: milestone)
create(:merge_request, :simple, source_project: project)
visit_merge_requests(project)
filter_by_milestone(milestone.title)
expect(page).to have_issuable_counts(open: 1, closed: 0, all: 1)
expect(page).to have_css('.merge-request', count: 1)
end
end
def visit_merge_requests(project)
visit namespace_project_merge_requests_path(project.namespace, project)
end
......
......@@ -9,6 +9,7 @@
"iid": { "type": "integer" },
"title": { "type": "string" },
"confidential": { "type": "boolean" },
"due_date": { "type": ["date", "null"] },
"labels": {
"type": "array",
"items": {
......@@ -42,7 +43,8 @@
"name": { "type": "string" },
"username": { "type": "string" },
"avatar_url": { "type": "uri" }
}
},
"subscribed": { "type": ["boolean", "null"] }
},
"additionalProperties": false
}
/* eslint-disable */
/*= require abuse_reports */
/*= require jquery */
......
/*= require jquery.cookie.js */
/* eslint-disable */
/*= require js.cookie.js */
/*= require jquery.endless-scroll.js */
/*= require pager */
/*= require activities */
......
/* eslint-disable */
/*= require lib/utils/common_utils */
......
/* eslint-disable */
/*= require awards_handler */
/*= require jquery */
/*= require jquery.cookie */
/*= require js.cookie */
/*= require ./fixtures/emoji_menu */
(function() {
......@@ -44,7 +45,6 @@
spyOn(jQuery, 'get').and.callFake(function(req, cb) {
return cb(window.emojiMenu);
});
spyOn(jQuery, 'cookie');
});
afterEach(function() {
// restore original url root value
......@@ -190,28 +190,6 @@
return expect($thumbsUpEmoji.data("original-title")).toBe('sam');
});
});
describe('::addEmojiToFrequentlyUsedList', function() {
it('should set a cookie with the correct default path', function() {
gon.relative_url_root = '';
awardsHandler.addEmojiToFrequentlyUsedList('sunglasses');
expect(jQuery.cookie)
.toHaveBeenCalledWith('frequently_used_emojis', 'sunglasses', {
path: '/',
expires: 365
})
;
});
it('should set a cookie with the correct custom root path', function() {
gon.relative_url_root = '/gitlab/subdir';
awardsHandler.addEmojiToFrequentlyUsedList('alien');
expect(jQuery.cookie)
.toHaveBeenCalledWith('frequently_used_emojis', 'alien', {
path: '/gitlab/subdir',
expires: 365
})
;
});
});
describe('search', function() {
return it('should filter the emoji', function() {
$('.js-add-award').eq(0).click();
......
/* eslint-disable */
/*= require behaviors/autosize */
......
/* eslint-disable */
/*= require behaviors/quick_submit */
......
/* eslint-disable */
/*= require behaviors/requires_input */
......
/* eslint-disable */
//= require jquery
//= require jquery_ujs
//= require jquery.cookie
//= require js.cookie
//= require vue
//= require vue-resource
//= require lib/utils/url_utility
......@@ -17,7 +18,7 @@
gl.boardService = new BoardService('/test/issue-boards/board', '1');
gl.issueBoards.BoardsStore.create();
$.cookie('issue_board_welcome_hidden', 'false');
Cookies.set('issue_board_welcome_hidden', 'false');
});
describe('Store', () => {
......
/* eslint-disable */
//= require jquery
//= require jquery_ujs
//= require jquery.cookie
//= require js.cookie
//= require vue
//= require vue-resource
//= require lib/utils/url_utility
......
/* eslint-disable */
//= require jquery
//= require jquery_ujs
//= require jquery.cookie
//= require js.cookie
//= require vue
//= require vue-resource
//= require lib/utils/url_utility
......
/* eslint-disable */
const listObj = {
id: 1,
position: 0,
......
/* eslint-disable */
//= require lib/utils/datetime_utility
(() => {
describe('Date time utils', () => {
......
/* eslint-disable */
//= require vue
//= require diff_notes/models/discussion
//= require diff_notes/models/note
......
/* eslint-disable */
/*= require extensions/array */
......
/* eslint-disable */
/*= require extensions/jquery */
......
/* eslint-disable */
(function() {
window.emojiMenu = "<div class='emoji-menu'>\n <input type=\"text\" name=\"emoji_search\" id=\"emoji_search\" value=\"\" class=\"emoji-search search-input form-control\" />\n <div class='emoji-menu-content'>\n <h5 class='emoji-menu-title'>\n Emoticons\n </h5>\n <ul class='clearfix emoji-menu-list'>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F47D\" title=\"alien\" data-aliases=\"\" data-emoji=\"alien\" data-unicode-name=\"1F47D\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F47C\" title=\"angel\" data-aliases=\"\" data-emoji=\"angel\" data-unicode-name=\"1F47C\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F4A2\" title=\"anger\" data-aliases=\"\" data-emoji=\"anger\" data-unicode-name=\"1F4A2\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F620\" title=\"angry\" data-aliases=\"\" data-emoji=\"angry\" data-unicode-name=\"1F620\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F627\" title=\"anguished\" data-aliases=\"\" data-emoji=\"anguished\" data-unicode-name=\"1F627\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F632\" title=\"astonished\" data-aliases=\"\" data-emoji=\"astonished\" data-unicode-name=\"1F632\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F45F\" title=\"athletic_shoe\" data-aliases=\"\" data-emoji=\"athletic_shoe\" data-unicode-name=\"1F45F\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F476\" title=\"baby\" data-aliases=\"\" data-emoji=\"baby\" data-unicode-name=\"1F476\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F459\" title=\"bikini\" data-aliases=\"\" data-emoji=\"bikini\" data-unicode-name=\"1F459\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F499\" title=\"blue_heart\" data-aliases=\"\" data-emoji=\"blue_heart\" data-unicode-name=\"1F499\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F60A\" title=\"blush\" data-aliases=\"\" data-emoji=\"blush\" data-unicode-name=\"1F60A\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F4A5\" title=\"boom\" data-aliases=\"\" data-emoji=\"boom\" data-unicode-name=\"1F4A5\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F462\" title=\"boot\" data-aliases=\"\" data-emoji=\"boot\" data-unicode-name=\"1F462\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F647\" title=\"bow\" data-aliases=\"\" data-emoji=\"bow\" data-unicode-name=\"1F647\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F466\" title=\"boy\" data-aliases=\"\" data-emoji=\"boy\" data-unicode-name=\"1F466\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F470\" title=\"bride_with_veil\" data-aliases=\"\" data-emoji=\"bride_with_veil\" data-unicode-name=\"1F470\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F4BC\" title=\"briefcase\" data-aliases=\"\" data-emoji=\"briefcase\" data-unicode-name=\"1F4BC\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F494\" title=\"broken_heart\" data-aliases=\"\" data-emoji=\"broken_heart\" data-unicode-name=\"1F494\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F464\" title=\"bust_in_silhouette\" data-aliases=\"\" data-emoji=\"bust_in_silhouette\" data-unicode-name=\"1F464\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F465\" title=\"busts_in_silhouette\" data-aliases=\"\" data-emoji=\"busts_in_silhouette\" data-unicode-name=\"1F465\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F44F\" title=\"clap\" data-aliases=\"\" data-emoji=\"clap\" data-unicode-name=\"1F44F\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F302\" title=\"closed_umbrella\" data-aliases=\"\" data-emoji=\"closed_umbrella\" data-unicode-name=\"1F302\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F630\" title=\"cold_sweat\" data-aliases=\"\" data-emoji=\"cold_sweat\" data-unicode-name=\"1F630\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F616\" title=\"confounded\" data-aliases=\"\" data-emoji=\"confounded\" data-unicode-name=\"1F616\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F615\" title=\"confused\" data-aliases=\"\" data-emoji=\"confused\" data-unicode-name=\"1F615\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F477\" title=\"construction_worker\" data-aliases=\"\" data-emoji=\"construction_worker\" data-unicode-name=\"1F477\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F46E\" title=\"cop\" data-aliases=\"\" data-emoji=\"cop\" data-unicode-name=\"1F46E\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F46B\" title=\"couple\" data-aliases=\"\" data-emoji=\"couple\" data-unicode-name=\"1F46B\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F491\" title=\"couple_with_heart\" data-aliases=\"\" data-emoji=\"couple_with_heart\" data-unicode-name=\"1F491\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F48F\" title=\"couplekiss\" data-aliases=\"\" data-emoji=\"couplekiss\" data-unicode-name=\"1F48F\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F451\" title=\"crown\" data-aliases=\"\" data-emoji=\"crown\" data-unicode-name=\"1F451\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F622\" title=\"cry\" data-aliases=\"\" data-emoji=\"cry\" data-unicode-name=\"1F622\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F63F\" title=\"crying_cat_face\" data-aliases=\"\" data-emoji=\"crying_cat_face\" data-unicode-name=\"1F63F\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F498\" title=\"cupid\" data-aliases=\"\" data-emoji=\"cupid\" data-unicode-name=\"1F498\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F483\" title=\"dancer\" data-aliases=\"\" data-emoji=\"dancer\" data-unicode-name=\"1F483\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F46F\" title=\"dancers\" data-aliases=\"\" data-emoji=\"dancers\" data-unicode-name=\"1F46F\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F4A8\" title=\"dash\" data-aliases=\"\" data-emoji=\"dash\" data-unicode-name=\"1F4A8\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F61E\" title=\"disappointed\" data-aliases=\"\" data-emoji=\"disappointed\" data-unicode-name=\"1F61E\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F625\" title=\"disappointed_relieved\" data-aliases=\"\" data-emoji=\"disappointed_relieved\" data-unicode-name=\"1F625\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F4AB\" title=\"dizzy\" data-aliases=\"\" data-emoji=\"dizzy\" data-unicode-name=\"1F4AB\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F635\" title=\"dizzy_face\" data-aliases=\"\" data-emoji=\"dizzy_face\" data-unicode-name=\"1F635\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F457\" title=\"dress\" data-aliases=\"\" data-emoji=\"dress\" data-unicode-name=\"1F457\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F4A7\" title=\"droplet\" data-aliases=\"\" data-emoji=\"droplet\" data-unicode-name=\"1F4A7\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F442\" title=\"ear\" data-aliases=\"\" data-emoji=\"ear\" data-unicode-name=\"1F442\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F611\" title=\"expressionless\" data-aliases=\"\" data-emoji=\"expressionless\" data-unicode-name=\"1F611\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F453\" title=\"eyeglasses\" data-aliases=\"\" data-emoji=\"eyeglasses\" data-unicode-name=\"1F453\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F440\" title=\"eyes\" data-aliases=\"\" data-emoji=\"eyes\" data-unicode-name=\"1F440\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F46A\" title=\"family\" data-aliases=\"\" data-emoji=\"family\" data-unicode-name=\"1F46A\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F628\" title=\"fearful\" data-aliases=\"\" data-emoji=\"fearful\" data-unicode-name=\"1F628\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F525\" title=\"fire\" data-aliases=\":flame:\" data-emoji=\"fire\" data-unicode-name=\"1F525\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-270A\" title=\"fist\" data-aliases=\"\" data-emoji=\"fist\" data-unicode-name=\"270A\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F633\" title=\"flushed\" data-aliases=\"\" data-emoji=\"flushed\" data-unicode-name=\"1F633\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F463\" title=\"footprints\" data-aliases=\"\" data-emoji=\"footprints\" data-unicode-name=\"1F463\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F626\" title=\"frowning\" data-aliases=\":anguished:\" data-emoji=\"frowning\" data-unicode-name=\"1F626\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F48E\" title=\"gem\" data-aliases=\"\" data-emoji=\"gem\" data-unicode-name=\"1F48E\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F467\" title=\"girl\" data-aliases=\"\" data-emoji=\"girl\" data-unicode-name=\"1F467\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F49A\" title=\"green_heart\" data-aliases=\"\" data-emoji=\"green_heart\" data-unicode-name=\"1F49A\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F62C\" title=\"grimacing\" data-aliases=\"\" data-emoji=\"grimacing\" data-unicode-name=\"1F62C\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F601\" title=\"grin\" data-aliases=\"\" data-emoji=\"grin\" data-unicode-name=\"1F601\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F600\" title=\"grinning\" data-aliases=\"\" data-emoji=\"grinning\" data-unicode-name=\"1F600\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F482\" title=\"guardsman\" data-aliases=\"\" data-emoji=\"guardsman\" data-unicode-name=\"1F482\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F487\" title=\"haircut\" data-aliases=\"\" data-emoji=\"haircut\" data-unicode-name=\"1F487\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F45C\" title=\"handbag\" data-aliases=\"\" data-emoji=\"handbag\" data-unicode-name=\"1F45C\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F649\" title=\"hear_no_evil\" data-aliases=\"\" data-emoji=\"hear_no_evil\" data-unicode-name=\"1F649\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-2764\" title=\"heart\" data-aliases=\"\" data-emoji=\"heart\" data-unicode-name=\"2764\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F60D\" title=\"heart_eyes\" data-aliases=\"\" data-emoji=\"heart_eyes\" data-unicode-name=\"1F60D\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F63B\" title=\"heart_eyes_cat\" data-aliases=\"\" data-emoji=\"heart_eyes_cat\" data-unicode-name=\"1F63B\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F493\" title=\"heartbeat\" data-aliases=\"\" data-emoji=\"heartbeat\" data-unicode-name=\"1F493\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F497\" title=\"heartpulse\" data-aliases=\"\" data-emoji=\"heartpulse\" data-unicode-name=\"1F497\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F460\" title=\"high_heel\" data-aliases=\"\" data-emoji=\"high_heel\" data-unicode-name=\"1F460\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F62F\" title=\"hushed\" data-aliases=\"\" data-emoji=\"hushed\" data-unicode-name=\"1F62F\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F47F\" title=\"imp\" data-aliases=\"\" data-emoji=\"imp\" data-unicode-name=\"1F47F\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F481\" title=\"information_desk_person\" data-aliases=\"\" data-emoji=\"information_desk_person\" data-unicode-name=\"1F481\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F607\" title=\"innocent\" data-aliases=\"\" data-emoji=\"innocent\" data-unicode-name=\"1F607\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F47A\" title=\"japanese_goblin\" data-aliases=\"\" data-emoji=\"japanese_goblin\" data-unicode-name=\"1F47A\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F479\" title=\"japanese_ogre\" data-aliases=\"\" data-emoji=\"japanese_ogre\" data-unicode-name=\"1F479\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F456\" title=\"jeans\" data-aliases=\"\" data-emoji=\"jeans\" data-unicode-name=\"1F456\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F602\" title=\"joy\" data-aliases=\"\" data-emoji=\"joy\" data-unicode-name=\"1F602\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F639\" title=\"joy_cat\" data-aliases=\"\" data-emoji=\"joy_cat\" data-unicode-name=\"1F639\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F458\" title=\"kimono\" data-aliases=\"\" data-emoji=\"kimono\" data-unicode-name=\"1F458\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F48B\" title=\"kiss\" data-aliases=\"\" data-emoji=\"kiss\" data-unicode-name=\"1F48B\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F617\" title=\"kissing\" data-aliases=\"\" data-emoji=\"kissing\" data-unicode-name=\"1F617\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F63D\" title=\"kissing_cat\" data-aliases=\"\" data-emoji=\"kissing_cat\" data-unicode-name=\"1F63D\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F61A\" title=\"kissing_closed_eyes\" data-aliases=\"\" data-emoji=\"kissing_closed_eyes\" data-unicode-name=\"1F61A\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F618\" title=\"kissing_heart\" data-aliases=\"\" data-emoji=\"kissing_heart\" data-unicode-name=\"1F618\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F619\" title=\"kissing_smiling_eyes\" data-aliases=\"\" data-emoji=\"kissing_smiling_eyes\" data-unicode-name=\"1F619\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F606\" title=\"laughing\" data-aliases=\":satisfied:\" data-emoji=\"laughing\" data-unicode-name=\"1F606\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F444\" title=\"lips\" data-aliases=\"\" data-emoji=\"lips\" data-unicode-name=\"1F444\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F484\" title=\"lipstick\" data-aliases=\"\" data-emoji=\"lipstick\" data-unicode-name=\"1F484\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F48C\" title=\"love_letter\" data-aliases=\"\" data-emoji=\"love_letter\" data-unicode-name=\"1F48C\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F468\" title=\"man\" data-aliases=\"\" data-emoji=\"man\" data-unicode-name=\"1F468\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F472\" title=\"man_with_gua_pi_mao\" data-aliases=\"\" data-emoji=\"man_with_gua_pi_mao\" data-unicode-name=\"1F472\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F473\" title=\"man_with_turban\" data-aliases=\"\" data-emoji=\"man_with_turban\" data-unicode-name=\"1F473\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F45E\" title=\"mans_shoe\" data-aliases=\"\" data-emoji=\"mans_shoe\" data-unicode-name=\"1F45E\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F637\" title=\"mask\" data-aliases=\"\" data-emoji=\"mask\" data-unicode-name=\"1F637\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F486\" title=\"massage\" data-aliases=\"\" data-emoji=\"massage\" data-unicode-name=\"1F486\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F4AA\" title=\"muscle\" data-aliases=\"\" data-emoji=\"muscle\" data-unicode-name=\"1F4AA\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F485\" title=\"nail_care\" data-aliases=\"\" data-emoji=\"nail_care\" data-unicode-name=\"1F485\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F454\" title=\"necktie\" data-aliases=\"\" data-emoji=\"necktie\" data-unicode-name=\"1F454\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F610\" title=\"neutral_face\" data-aliases=\"\" data-emoji=\"neutral_face\" data-unicode-name=\"1F610\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F645\" title=\"no_good\" data-aliases=\"\" data-emoji=\"no_good\" data-unicode-name=\"1F645\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F636\" title=\"no_mouth\" data-aliases=\"\" data-emoji=\"no_mouth\" data-unicode-name=\"1F636\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F443\" title=\"nose\" data-aliases=\"\" data-emoji=\"nose\" data-unicode-name=\"1F443\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F44C\" title=\"ok_hand\" data-aliases=\"\" data-emoji=\"ok_hand\" data-unicode-name=\"1F44C\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F646\" title=\"ok_woman\" data-aliases=\"\" data-emoji=\"ok_woman\" data-unicode-name=\"1F646\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F474\" title=\"older_man\" data-aliases=\"\" data-emoji=\"older_man\" data-unicode-name=\"1F474\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F475\" title=\"older_woman\" data-aliases=\":grandma:\" data-emoji=\"older_woman\" data-unicode-name=\"1F475\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F450\" title=\"open_hands\" data-aliases=\"\" data-emoji=\"open_hands\" data-unicode-name=\"1F450\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F62E\" title=\"open_mouth\" data-aliases=\"\" data-emoji=\"open_mouth\" data-unicode-name=\"1F62E\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F614\" title=\"pensive\" data-aliases=\"\" data-emoji=\"pensive\" data-unicode-name=\"1F614\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F623\" title=\"persevere\" data-aliases=\"\" data-emoji=\"persevere\" data-unicode-name=\"1F623\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F64D\" title=\"person_frowning\" data-aliases=\"\" data-emoji=\"person_frowning\" data-unicode-name=\"1F64D\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F471\" title=\"person_with_blond_hair\" data-aliases=\"\" data-emoji=\"person_with_blond_hair\" data-unicode-name=\"1F471\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F64E\" title=\"person_with_pouting_face\" data-aliases=\"\" data-emoji=\"person_with_pouting_face\" data-unicode-name=\"1F64E\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F447\" title=\"point_down\" data-aliases=\"\" data-emoji=\"point_down\" data-unicode-name=\"1F447\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F448\" title=\"point_left\" data-aliases=\"\" data-emoji=\"point_left\" data-unicode-name=\"1F448\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F449\" title=\"point_right\" data-aliases=\"\" data-emoji=\"point_right\" data-unicode-name=\"1F449\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-261D\" title=\"point_up\" data-aliases=\"\" data-emoji=\"point_up\" data-unicode-name=\"261D\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F446\" title=\"point_up_2\" data-aliases=\"\" data-emoji=\"point_up_2\" data-unicode-name=\"1F446\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F4A9\" title=\"poop\" data-aliases=\":shit: :hankey: :poo:\" data-emoji=\"poop\" data-unicode-name=\"1F4A9\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F45D\" title=\"pouch\" data-aliases=\"\" data-emoji=\"pouch\" data-unicode-name=\"1F45D\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F63E\" title=\"pouting_cat\" data-aliases=\"\" data-emoji=\"pouting_cat\" data-unicode-name=\"1F63E\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F64F\" title=\"pray\" data-aliases=\"\" data-emoji=\"pray\" data-unicode-name=\"1F64F\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F478\" title=\"princess\" data-aliases=\"\" data-emoji=\"princess\" data-unicode-name=\"1F478\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F44A\" title=\"punch\" data-aliases=\"\" data-emoji=\"punch\" data-unicode-name=\"1F44A\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F49C\" title=\"purple_heart\" data-aliases=\"\" data-emoji=\"purple_heart\" data-unicode-name=\"1F49C\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F45B\" title=\"purse\" data-aliases=\"\" data-emoji=\"purse\" data-unicode-name=\"1F45B\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F621\" title=\"rage\" data-aliases=\"\" data-emoji=\"rage\" data-unicode-name=\"1F621\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-270B\" title=\"raised_hand\" data-aliases=\"\" data-emoji=\"raised_hand\" data-unicode-name=\"270B\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F64C\" title=\"raised_hands\" data-aliases=\"\" data-emoji=\"raised_hands\" data-unicode-name=\"1F64C\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F64B\" title=\"raising_hand\" data-aliases=\"\" data-emoji=\"raising_hand\" data-unicode-name=\"1F64B\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-263A\" title=\"relaxed\" data-aliases=\"\" data-emoji=\"relaxed\" data-unicode-name=\"263A\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F60C\" title=\"relieved\" data-aliases=\"\" data-emoji=\"relieved\" data-unicode-name=\"1F60C\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F49E\" title=\"revolving_hearts\" data-aliases=\"\" data-emoji=\"revolving_hearts\" data-unicode-name=\"1F49E\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F380\" title=\"ribbon\" data-aliases=\"\" data-emoji=\"ribbon\" data-unicode-name=\"1F380\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F48D\" title=\"ring\" data-aliases=\"\" data-emoji=\"ring\" data-unicode-name=\"1F48D\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F3C3\" title=\"runner\" data-aliases=\"\" data-emoji=\"runner\" data-unicode-name=\"1F3C3\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F3BD\" title=\"running_shirt_with_sash\" data-aliases=\"\" data-emoji=\"running_shirt_with_sash\" data-unicode-name=\"1F3BD\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F461\" title=\"sandal\" data-aliases=\"\" data-emoji=\"sandal\" data-unicode-name=\"1F461\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F631\" title=\"scream\" data-aliases=\"\" data-emoji=\"scream\" data-unicode-name=\"1F631\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F640\" title=\"scream_cat\" data-aliases=\"\" data-emoji=\"scream_cat\" data-unicode-name=\"1F640\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F648\" title=\"see_no_evil\" data-aliases=\"\" data-emoji=\"see_no_evil\" data-unicode-name=\"1F648\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F455\" title=\"shirt\" data-aliases=\"\" data-emoji=\"shirt\" data-unicode-name=\"1F455\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F480\" title=\"skull\" data-aliases=\":skeleton:\" data-emoji=\"skull\" data-unicode-name=\"1F480\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F634\" title=\"sleeping\" data-aliases=\"\" data-emoji=\"sleeping\" data-unicode-name=\"1F634\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F62A\" title=\"sleepy\" data-aliases=\"\" data-emoji=\"sleepy\" data-unicode-name=\"1F62A\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F604\" title=\"smile\" data-aliases=\"\" data-emoji=\"smile\" data-unicode-name=\"1F604\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F638\" title=\"smile_cat\" data-aliases=\"\" data-emoji=\"smile_cat\" data-unicode-name=\"1F638\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F603\" title=\"smiley\" data-aliases=\"\" data-emoji=\"smiley\" data-unicode-name=\"1F603\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F63A\" title=\"smiley_cat\" data-aliases=\"\" data-emoji=\"smiley_cat\" data-unicode-name=\"1F63A\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F608\" title=\"smiling_imp\" data-aliases=\"\" data-emoji=\"smiling_imp\" data-unicode-name=\"1F608\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F60F\" title=\"smirk\" data-aliases=\"\" data-emoji=\"smirk\" data-unicode-name=\"1F60F\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F63C\" title=\"smirk_cat\" data-aliases=\"\" data-emoji=\"smirk_cat\" data-unicode-name=\"1F63C\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F62D\" title=\"sob\" data-aliases=\"\" data-emoji=\"sob\" data-unicode-name=\"1F62D\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-2728\" title=\"sparkles\" data-aliases=\"\" data-emoji=\"sparkles\" data-unicode-name=\"2728\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F496\" title=\"sparkling_heart\" data-aliases=\"\" data-emoji=\"sparkling_heart\" data-unicode-name=\"1F496\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F64A\" title=\"speak_no_evil\" data-aliases=\"\" data-emoji=\"speak_no_evil\" data-unicode-name=\"1F64A\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F4AC\" title=\"speech_balloon\" data-aliases=\"\" data-emoji=\"speech_balloon\" data-unicode-name=\"1F4AC\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F31F\" title=\"star2\" data-aliases=\"\" data-emoji=\"star2\" data-unicode-name=\"1F31F\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F61B\" title=\"stuck_out_tongue\" data-aliases=\"\" data-emoji=\"stuck_out_tongue\" data-unicode-name=\"1F61B\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F61D\" title=\"stuck_out_tongue_closed_eyes\" data-aliases=\"\" data-emoji=\"stuck_out_tongue_closed_eyes\" data-unicode-name=\"1F61D\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F61C\" title=\"stuck_out_tongue_winking_eye\" data-aliases=\"\" data-emoji=\"stuck_out_tongue_winking_eye\" data-unicode-name=\"1F61C\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F60E\" title=\"sunglasses\" data-aliases=\"\" data-emoji=\"sunglasses\" data-unicode-name=\"1F60E\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F613\" title=\"sweat\" data-aliases=\"\" data-emoji=\"sweat\" data-unicode-name=\"1F613\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F4A6\" title=\"sweat_drops\" data-aliases=\"\" data-emoji=\"sweat_drops\" data-unicode-name=\"1F4A6\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F605\" title=\"sweat_smile\" data-aliases=\"\" data-emoji=\"sweat_smile\" data-unicode-name=\"1F605\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F4AD\" title=\"thought_balloon\" data-aliases=\"\" data-emoji=\"thought_balloon\" data-unicode-name=\"1F4AD\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F44E\" title=\"thumbsdown\" data-aliases=\":-1:\" data-emoji=\"thumbsdown\" data-unicode-name=\"1F44E\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F44D\" title=\"thumbsup\" data-aliases=\":+1:\" data-emoji=\"thumbsup\" data-unicode-name=\"1F44D\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F62B\" title=\"tired_face\" data-aliases=\"\" data-emoji=\"tired_face\" data-unicode-name=\"1F62B\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F445\" title=\"tongue\" data-aliases=\"\" data-emoji=\"tongue\" data-unicode-name=\"1F445\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F3A9\" title=\"tophat\" data-aliases=\"\" data-emoji=\"tophat\" data-unicode-name=\"1F3A9\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F624\" title=\"triumph\" data-aliases=\"\" data-emoji=\"triumph\" data-unicode-name=\"1F624\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F495\" title=\"two_hearts\" data-aliases=\"\" data-emoji=\"two_hearts\" data-unicode-name=\"1F495\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F46C\" title=\"two_men_holding_hands\" data-aliases=\"\" data-emoji=\"two_men_holding_hands\" data-unicode-name=\"1F46C\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F46D\" title=\"two_women_holding_hands\" data-aliases=\"\" data-emoji=\"two_women_holding_hands\" data-unicode-name=\"1F46D\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F612\" title=\"unamused\" data-aliases=\"\" data-emoji=\"unamused\" data-unicode-name=\"1F612\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-270C\" title=\"v\" data-aliases=\"\" data-emoji=\"v\" data-unicode-name=\"270C\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F6B6\" title=\"walking\" data-aliases=\"\" data-emoji=\"walking\" data-unicode-name=\"1F6B6\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F44B\" title=\"wave\" data-aliases=\"\" data-emoji=\"wave\" data-unicode-name=\"1F44B\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F629\" title=\"weary\" data-aliases=\"\" data-emoji=\"weary\" data-unicode-name=\"1F629\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F609\" title=\"wink\" data-aliases=\"\" data-emoji=\"wink\" data-unicode-name=\"1F609\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F469\" title=\"woman\" data-aliases=\"\" data-emoji=\"woman\" data-unicode-name=\"1F469\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F45A\" title=\"womans_clothes\" data-aliases=\"\" data-emoji=\"womans_clothes\" data-unicode-name=\"1F45A\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F452\" title=\"womans_hat\" data-aliases=\"\" data-emoji=\"womans_hat\" data-unicode-name=\"1F452\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F61F\" title=\"worried\" data-aliases=\"\" data-emoji=\"worried\" data-unicode-name=\"1F61F\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F49B\" title=\"yellow_heart\" data-aliases=\"\" data-emoji=\"yellow_heart\" data-unicode-name=\"1F49B\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F60B\" title=\"yum\" data-aliases=\"\" data-emoji=\"yum\" data-unicode-name=\"1F60B\"></div>\n </button>\n </li>\n <li class='pull-left text-center emoji-menu-list-item'>\n <button class='emoji-menu-btn text-center js-emoji-btn' type='button'>\n <div class=\"icon emoji-icon emoji-1F4A4\" title=\"zzz\" data-aliases=\"\" data-emoji=\"zzz\" data-unicode-name=\"1F4A4\"></div>\n </button>\n </li>\n </ul>\n </div>\n</div>";
......
/* eslint-disable */
/*= require jquery */
/*= require gl_dropdown */
/*= require turbolinks */
......
/* eslint-disable */
//= require jquery
//= require gl_field_errors
......
/* eslint-disable */
//= require graphs/stat_graph_contributors_graph
describe("ContributorsGraph", function () {
......
/* eslint-disable */
//= require graphs/stat_graph_contributors_util
describe("ContributorsStatGraphUtil", function () {
......
/* eslint-disable */
//= require graphs/stat_graph
describe("StatGraph", function () {
......
/* eslint-disable */
/*= require lib/utils/text_utility */
/*= require issue */
......
/* eslint-disable */
//= require lib/utils/type_utility
//= require jquery
//= require bootstrap
......
/* eslint-disable */
/*= require line_highlighter */
......
/* eslint-disable */
/*= require merge_request */
......
/* eslint-disable */
/*= require merge_request_tabs */
//= require breakpoints
......
/* eslint-disable */
/*= require merge_request_widget */
/*= require lib/utils/jquery.timeago.js */
......
/* eslint-disable */
/*= require jquery-ui/autocomplete */
/*= require new_branch_form */
......
/* eslint-disable */
/*= require notes */
/*= require autosize */
/*= require gl_form */
......
/* eslint-disable */
/*= require bootstrap */
/*= require select2 */
......
/* eslint-disable */
/*= require right_sidebar */
/*= require jquery */
/*= require jquery.cookie */
/*= require js.cookie */
(function() {
var $aside, $icon, $labelsIcon, $page, $toggle, assertSidebarState;
......
/* eslint-disable */
/*= require gl_dropdown */
/*= require search_autocomplete */
......
/* eslint-disable */
/*= require shortcuts_issuable */
......
/* eslint-disable */
// PhantomJS (Teaspoons default driver) doesn't have support for
// Function.prototype.bind, which has caused confusion. Use this polyfill to
// avoid the confusion.
......
/* eslint-disable */
/*= require syntax_highlight */
......
/* eslint-disable */
/*= require u2f/authenticate */
/*= require u2f/util */
......
/* eslint-disable */
(function() {
var bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; };
......
/* eslint-disable */
/*= require u2f/register */
/*= require u2f/util */
......
/* eslint-disable */
/*= require zen_mode */
......
......@@ -6,6 +6,7 @@ describe API::API, api: true do
let(:user) { create(:user) }
let(:admin) { create(:admin) }
let(:project) { create(:project, creator_id: user.id) }
let(:project2) { create(:project, creator_id: user.id) }
let(:deploy_key) { create(:deploy_key, public: true) }
let!(:deploy_keys_project) do
......@@ -96,6 +97,22 @@ describe API::API, api: true do
post api("/projects/#{project.id}/deploy_keys", admin), key_attrs
end.to change{ project.deploy_keys.count }.by(1)
end
it 'returns an existing ssh key when attempting to add a duplicate' do
expect do
post api("/projects/#{project.id}/deploy_keys", admin), { key: deploy_key.key, title: deploy_key.title }
end.not_to change { project.deploy_keys.count }
expect(response).to have_http_status(201)
end
it 'joins an existing ssh key to a new project' do
expect do
post api("/projects/#{project2.id}/deploy_keys", admin), { key: deploy_key.key, title: deploy_key.title }
end.to change { project2.deploy_keys.count }.by(1)
expect(response).to have_http_status(201)
end
end
describe 'DELETE /projects/:id/deploy_keys/:key_id' do
......
/**
* jQuery Cookie plugin
*
* Copyright (c) 2010 Klaus Hartl (stilbuero.de)
* Dual licensed under the MIT and GPL licenses:
* http://www.opensource.org/licenses/mit-license.php
* http://www.gnu.org/licenses/gpl.html
*
*/
jQuery.cookie = function (key, value, options) {
// key and at least value given, set cookie...
if (arguments.length > 1 && String(value) !== "[object Object]") {
options = jQuery.extend({}, options);
if (value === null || value === undefined) {
options.expires = -1;
}
if (typeof options.expires === 'number') {
var days = options.expires, t = options.expires = new Date();
t.setDate(t.getDate() + days);
}
value = String(value);
return (document.cookie = [
encodeURIComponent(key), '=',
options.raw ? value : encodeURIComponent(value),
options.expires ? '; expires=' + options.expires.toUTCString() : '', // use expires attribute, max-age is not supported by IE
options.path ? '; path=' + options.path : '',
options.domain ? '; domain=' + options.domain : '',
options.secure ? '; secure' : ''
].join(''));
}
// key and possibly options given, get cookie...
options = value || {};
var result, decode = options.raw ? function (s) { return s; } : decodeURIComponent;
return (result = new RegExp('(?:^|; )' + encodeURIComponent(key) + '=([^;]*)').exec(document.cookie)) ? decode(result[1]) : null;
};
/*!
* JavaScript Cookie v2.1.3
* https://github.com/js-cookie/js-cookie
*
* Copyright 2006, 2015 Klaus Hartl & Fagner Brack
* Released under the MIT license
*/
;(function (factory) {
var registeredInModuleLoader = false;
if (typeof define === 'function' && define.amd) {
define(factory);
registeredInModuleLoader = true;
}
if (typeof exports === 'object') {
module.exports = factory();
registeredInModuleLoader = true;
}
if (!registeredInModuleLoader) {
var OldCookies = window.Cookies;
var api = window.Cookies = factory();
api.noConflict = function () {
window.Cookies = OldCookies;
return api;
};
}
}(function () {
function extend () {
var i = 0;
var result = {};
for (; i < arguments.length; i++) {
var attributes = arguments[ i ];
for (var key in attributes) {
result[key] = attributes[key];
}
}
return result;
}
function init (converter) {
function api (key, value, attributes) {
var result;
if (typeof document === 'undefined') {
return;
}
// Write
if (arguments.length > 1) {
attributes = extend({
path: '/'
}, api.defaults, attributes);
if (typeof attributes.expires === 'number') {
var expires = new Date();
expires.setMilliseconds(expires.getMilliseconds() + attributes.expires * 864e+5);
attributes.expires = expires;
}
try {
result = JSON.stringify(value);
if (/^[\{\[]/.test(result)) {
value = result;
}
} catch (e) {}
if (!converter.write) {
value = encodeURIComponent(String(value))
.replace(/%(23|24|26|2B|3A|3C|3E|3D|2F|3F|40|5B|5D|5E|60|7B|7D|7C)/g, decodeURIComponent);
} else {
value = converter.write(value, key);
}
key = encodeURIComponent(String(key));
key = key.replace(/%(23|24|26|2B|5E|60|7C)/g, decodeURIComponent);
key = key.replace(/[\(\)]/g, escape);
return (document.cookie = [
key, '=', value,
attributes.expires ? '; expires=' + attributes.expires.toUTCString() : '', // use expires attribute, max-age is not supported by IE
attributes.path ? '; path=' + attributes.path : '',
attributes.domain ? '; domain=' + attributes.domain : '',
attributes.secure ? '; secure' : ''
].join(''));
}
// Read
if (!key) {
result = {};
}
// To prevent the for loop in the first place assign an empty array
// in case there are no cookies at all. Also prevents odd result when
// calling "get()"
var cookies = document.cookie ? document.cookie.split('; ') : [];
var rdecode = /(%[0-9A-Z]{2})+/g;
var i = 0;
for (; i < cookies.length; i++) {
var parts = cookies[i].split('=');
var cookie = parts.slice(1).join('=');
if (cookie.charAt(0) === '"') {
cookie = cookie.slice(1, -1);
}
try {
var name = parts[0].replace(rdecode, decodeURIComponent);
cookie = converter.read ?
converter.read(cookie, name) : converter(cookie, name) ||
cookie.replace(rdecode, decodeURIComponent);
if (this.json) {
try {
cookie = JSON.parse(cookie);
} catch (e) {}
}
if (key === name) {
result = cookie;
break;
}
if (!key) {
result[name] = cookie;
}
} catch (e) {}
}
return result;
}
api.set = api;
api.get = function (key) {
return api.call(api, key);
};
api.getJSON = function () {
return api.apply({
json: true
}, [].slice.call(arguments));
};
api.defaults = {};
api.remove = function (key, attributes) {
api(key, '', extend(attributes, {
expires: -1
}));
};
api.withConverter = init;
return api;
}
return init(function () {});
}));
\ No newline at end of file
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment