Commit f452cb77 authored by Phil Hughes's avatar Phil Hughes

Merge branch 'master' into issue-edit-inline

parents 2ef6172d 6ece9792

Too many changes to show.

To preserve performance only 1000 of 1000+ files are displayed.

...@@ -27,6 +27,7 @@ ...@@ -27,6 +27,7 @@
}, },
"rules": { "rules": {
"filenames/match-regex": [2, "^[a-z0-9_]+$"], "filenames/match-regex": [2, "^[a-z0-9_]+$"],
"import/no-commonjs": "error",
"no-multiple-empty-lines": ["error", { "max": 1 }], "no-multiple-empty-lines": ["error", { "max": 1 }],
"promise/catch-or-return": "error" "promise/catch-or-return": "error"
} }
......
...@@ -18,6 +18,7 @@ eslint-report.html ...@@ -18,6 +18,7 @@ eslint-report.html
.sass-cache/ .sass-cache/
/.secret /.secret
/.vagrant /.vagrant
/.yarn-cache
/.byebug_history /.byebug_history
/Vagrantfile /Vagrantfile
/backups/* /backups/*
......
This diff is collapsed.
...@@ -969,6 +969,12 @@ RSpec/DescribeSymbol: ...@@ -969,6 +969,12 @@ RSpec/DescribeSymbol:
RSpec/DescribedClass: RSpec/DescribedClass:
Enabled: true Enabled: true
# Checks if an example group does not include any tests.
RSpec/EmptyExampleGroup:
Enabled: true
CustomIncludeMethods:
- run_permission_checks
# Checks for long example. # Checks for long example.
RSpec/ExampleLength: RSpec/ExampleLength:
Enabled: false Enabled: false
...@@ -987,6 +993,10 @@ RSpec/ExampleWording: ...@@ -987,6 +993,10 @@ RSpec/ExampleWording:
RSpec/ExpectActual: RSpec/ExpectActual:
Enabled: true Enabled: true
# Checks for opportunities to use `expect { … }.to output`.
RSpec/ExpectOutput:
Enabled: true
# Checks the file and folder naming of the spec file. # Checks the file and folder naming of the spec file.
RSpec/FilePath: RSpec/FilePath:
Enabled: true Enabled: true
......
...@@ -10,11 +10,6 @@ ...@@ -10,11 +10,6 @@
RSpec/BeforeAfterAll: RSpec/BeforeAfterAll:
Enabled: false Enabled: false
# Offense count: 15
# Configuration parameters: CustomIncludeMethods.
RSpec/EmptyExampleGroup:
Enabled: false
# Offense count: 233 # Offense count: 233
RSpec/EmptyLineAfterFinalLet: RSpec/EmptyLineAfterFinalLet:
Enabled: false Enabled: false
...@@ -23,10 +18,6 @@ RSpec/EmptyLineAfterFinalLet: ...@@ -23,10 +18,6 @@ RSpec/EmptyLineAfterFinalLet:
RSpec/EmptyLineAfterSubject: RSpec/EmptyLineAfterSubject:
Enabled: false Enabled: false
# Offense count: 3
RSpec/ExpectOutput:
Enabled: false
# Offense count: 72 # Offense count: 72
# Configuration parameters: EnforcedStyle, SupportedStyles. # Configuration parameters: EnforcedStyle, SupportedStyles.
# SupportedStyles: implicit, each, example # SupportedStyles: implicit, each, example
......
This diff is collapsed.
9.2.0-pre 9.3.0-pre
/* eslint-disable func-names, space-before-function-paren, quotes, object-shorthand, camelcase, no-var, comma-dangle, prefer-arrow-callback, quote-props, no-param-reassign, max-len */ import $ from 'jquery';
var Api = { const Api = {
groupsPath: "/api/:version/groups.json", groupsPath: '/api/:version/groups.json',
groupPath: "/api/:version/groups/:id.json", groupPath: '/api/:version/groups/:id.json',
namespacesPath: "/api/:version/namespaces.json", namespacesPath: '/api/:version/namespaces.json',
groupProjectsPath: "/api/:version/groups/:id/projects.json", groupProjectsPath: '/api/:version/groups/:id/projects.json',
projectsPath: "/api/:version/projects.json?simple=true", projectsPath: '/api/:version/projects.json?simple=true',
labelsPath: "/:namespace_path/:project_path/labels", labelsPath: '/:namespace_path/:project_path/labels',
licensePath: "/api/:version/templates/licenses/:key", licensePath: '/api/:version/templates/licenses/:key',
gitignorePath: "/api/:version/templates/gitignores/:key", gitignorePath: '/api/:version/templates/gitignores/:key',
gitlabCiYmlPath: "/api/:version/templates/gitlab_ci_ymls/:key", gitlabCiYmlPath: '/api/:version/templates/gitlab_ci_ymls/:key',
dockerfilePath: "/api/:version/templates/dockerfiles/:key", dockerfilePath: '/api/:version/templates/dockerfiles/:key',
issuableTemplatePath: "/:namespace_path/:project_path/templates/:type/:key", issuableTemplatePath: '/:namespace_path/:project_path/templates/:type/:key',
group: function(group_id, callback) { usersPath: '/api/:version/users.json',
var url = Api.buildUrl(Api.groupPath)
.replace(':id', group_id); group(groupId, callback) {
const url = Api.buildUrl(Api.groupPath)
.replace(':id', groupId);
return $.ajax({ return $.ajax({
url: url, url,
dataType: "json" dataType: 'json',
}).done(function(group) { })
return callback(group); .done(group => callback(group));
});
}, },
// Return groups list. Filtered by query // Return groups list. Filtered by query
groups: function(query, options, callback) { groups(query, options, callback) {
var url = Api.buildUrl(Api.groupsPath); const url = Api.buildUrl(Api.groupsPath);
return $.ajax({ return $.ajax({
url: url, url,
data: $.extend({ data: Object.assign({
search: query, search: query,
per_page: 20 per_page: 20,
}, options), }, options),
dataType: "json" dataType: 'json',
}).done(function(groups) { })
return callback(groups); .done(groups => callback(groups));
});
}, },
// Return namespaces list. Filtered by query // Return namespaces list. Filtered by query
namespaces: function(query, callback) { namespaces(query, callback) {
var url = Api.buildUrl(Api.namespacesPath); const url = Api.buildUrl(Api.namespacesPath);
return $.ajax({ return $.ajax({
url: url, url,
data: { data: {
search: query, search: query,
per_page: 20 per_page: 20,
}, },
dataType: "json" dataType: 'json',
}).done(function(namespaces) { }).done(namespaces => callback(namespaces));
return callback(namespaces);
});
}, },
// Return projects list. Filtered by query // Return projects list. Filtered by query
projects: function(query, options, callback) { projects(query, options, callback) {
var url = Api.buildUrl(Api.projectsPath); const url = Api.buildUrl(Api.projectsPath);
return $.ajax({ return $.ajax({
url: url, url,
data: $.extend({ data: Object.assign({
search: query, search: query,
per_page: 20, per_page: 20,
membership: true membership: true,
}, options), }, options),
dataType: "json" dataType: 'json',
}).done(function(projects) { })
return callback(projects); .done(projects => callback(projects));
});
}, },
newLabel: function(namespace_path, project_path, data, callback) {
var url = Api.buildUrl(Api.labelsPath) newLabel(namespacePath, projectPath, data, callback) {
.replace(':namespace_path', namespace_path) const url = Api.buildUrl(Api.labelsPath)
.replace(':project_path', project_path); .replace(':namespace_path', namespacePath)
.replace(':project_path', projectPath);
return $.ajax({ return $.ajax({
url: url, url,
type: "POST", type: 'POST',
data: { 'label': data }, data: { label: data },
dataType: "json" dataType: 'json',
}).done(function(label) { })
return callback(label); .done(label => callback(label))
}).error(function(message) { .error(message => callback(message.responseJSON));
return callback(message.responseJSON);
});
}, },
// Return group projects list. Filtered by query // Return group projects list. Filtered by query
groupProjects: function(group_id, query, callback) { groupProjects(groupId, query, callback) {
var url = Api.buildUrl(Api.groupProjectsPath) const url = Api.buildUrl(Api.groupProjectsPath)
.replace(':id', group_id); .replace(':id', groupId);
return $.ajax({ return $.ajax({
url: url, url,
data: { data: {
search: query, search: query,
per_page: 20 per_page: 20,
}, },
dataType: "json" dataType: 'json',
}).done(function(projects) { })
return callback(projects); .done(projects => callback(projects));
});
}, },
// Return text for a specific license // Return text for a specific license
licenseText: function(key, data, callback) { licenseText(key, data, callback) {
var url = Api.buildUrl(Api.licensePath) const url = Api.buildUrl(Api.licensePath)
.replace(':key', key); .replace(':key', key);
return $.ajax({ return $.ajax({
url: url, url,
data: data data,
}).done(function(license) { })
return callback(license); .done(license => callback(license));
});
}, },
gitignoreText: function(key, callback) {
var url = Api.buildUrl(Api.gitignorePath) gitignoreText(key, callback) {
const url = Api.buildUrl(Api.gitignorePath)
.replace(':key', key); .replace(':key', key);
return $.get(url, function(gitignore) { return $.get(url, gitignore => callback(gitignore));
return callback(gitignore);
});
}, },
gitlabCiYml: function(key, callback) {
var url = Api.buildUrl(Api.gitlabCiYmlPath) gitlabCiYml(key, callback) {
const url = Api.buildUrl(Api.gitlabCiYmlPath)
.replace(':key', key); .replace(':key', key);
return $.get(url, function(file) { return $.get(url, file => callback(file));
return callback(file);
});
}, },
dockerfileYml: function(key, callback) {
var url = Api.buildUrl(Api.dockerfilePath).replace(':key', key); dockerfileYml(key, callback) {
const url = Api.buildUrl(Api.dockerfilePath).replace(':key', key);
$.get(url, callback); $.get(url, callback);
}, },
issueTemplate: function(namespacePath, projectPath, key, type, callback) {
var url = Api.buildUrl(Api.issuableTemplatePath) issueTemplate(namespacePath, projectPath, key, type, callback) {
const url = Api.buildUrl(Api.issuableTemplatePath)
.replace(':key', key) .replace(':key', key)
.replace(':type', type) .replace(':type', type)
.replace(':project_path', projectPath) .replace(':project_path', projectPath)
.replace(':namespace_path', namespacePath); .replace(':namespace_path', namespacePath);
$.ajax({ $.ajax({
url: url, url,
dataType: 'json' dataType: 'json',
}).done(function(file) { })
callback(null, file); .done(file => callback(null, file))
}).error(callback); .error(callback);
}, },
buildUrl: function(url) {
users(query, options) {
const url = Api.buildUrl(this.usersPath);
return Api.wrapAjaxCall({
url,
data: Object.assign({
search: query,
per_page: 20,
}, options),
dataType: 'json',
});
},
buildUrl(url) {
let urlRoot = '';
if (gon.relative_url_root != null) { if (gon.relative_url_root != null) {
url = gon.relative_url_root + url; urlRoot = gon.relative_url_root;
} }
return url.replace(':version', gon.api_version); return urlRoot + url.replace(':version', gon.api_version);
} },
wrapAjaxCall(options) {
return new Promise((resolve, reject) => {
// jQuery 2 is not Promises/A+ compatible (missing catch)
$.ajax(options) // eslint-disable-line promise/catch-or-return
.then(data => resolve(data),
(jqXHR, textStatus, errorThrown) => {
const error = new Error(`${options.url}: ${errorThrown}`);
error.textStatus = textStatus;
reject(error);
},
);
});
},
}; };
window.Api = Api; export default Api;
/* global Api */
export default class FileTemplateSelector { export default class FileTemplateSelector {
constructor(mediator) { constructor(mediator) {
this.mediator = mediator; this.mediator = mediator;
...@@ -65,4 +63,3 @@ export default class FileTemplateSelector { ...@@ -65,4 +63,3 @@ export default class FileTemplateSelector {
this.reportSelection(opts); this.reportSelection(opts);
} }
} }
/* global Api */ import Api from '../../api';
import FileTemplateSelector from '../file_template_selector'; import FileTemplateSelector from '../file_template_selector';
......
/* global Api */ import Api from '../../api';
import FileTemplateSelector from '../file_template_selector'; import FileTemplateSelector from '../file_template_selector';
......
/* global Api */ import Api from '../../api';
import FileTemplateSelector from '../file_template_selector'; import FileTemplateSelector from '../file_template_selector';
......
/* global Api */ import Api from '../../api';
import FileTemplateSelector from '../file_template_selector'; import FileTemplateSelector from '../file_template_selector';
......
...@@ -50,9 +50,9 @@ export default class BlobViewer { ...@@ -50,9 +50,9 @@ export default class BlobViewer {
if (this.copySourceBtn) { if (this.copySourceBtn) {
this.copySourceBtn.addEventListener('click', () => { this.copySourceBtn.addEventListener('click', () => {
if (this.copySourceBtn.classList.contains('disabled')) return; if (this.copySourceBtn.classList.contains('disabled')) return this.copySourceBtn.blur();
this.switchToViewer('simple'); return this.switchToViewer('simple');
}); });
} }
} }
...@@ -114,6 +114,7 @@ export default class BlobViewer { ...@@ -114,6 +114,7 @@ export default class BlobViewer {
$(viewer).syntaxHighlight(); $(viewer).syntaxHighlight();
this.$fileHolder.trigger('highlight:line'); this.$fileHolder.trigger('highlight:line');
gl.utils.handleLocationHash();
this.toggleCopyButtonState(); this.toggleCopyButtonState();
}) })
......
...@@ -6,23 +6,22 @@ import Vue from 'vue'; ...@@ -6,23 +6,22 @@ import Vue from 'vue';
import VueResource from 'vue-resource'; import VueResource from 'vue-resource';
import FilteredSearchBoards from './filtered_search_boards'; import FilteredSearchBoards from './filtered_search_boards';
import eventHub from './eventhub'; import eventHub from './eventhub';
import './models/issue';
require('./models/issue'); import './models/label';
require('./models/label'); import './models/list';
require('./models/list'); import './models/milestone';
require('./models/milestone'); import './models/assignee';
require('./models/assignee'); import './stores/boards_store';
require('./stores/boards_store'); import './stores/modal_store';
require('./stores/modal_store'); import './services/board_service';
require('./services/board_service'); import './mixins/modal_mixins';
require('./mixins/modal_mixins'); import './mixins/sortable_default_options';
require('./mixins/sortable_default_options'); import './filters/due_date_filters';
require('./filters/due_date_filters'); import './components/board';
require('./components/board'); import './components/board_sidebar';
require('./components/board_sidebar'); import './components/new_list_dropdown';
require('./components/new_list_dropdown'); import './components/modal/index';
require('./components/modal/index'); import '../vue_shared/vue_resource_interceptor';
require('../vue_shared/vue_resource_interceptor');
Vue.use(VueResource); Vue.use(VueResource);
......
...@@ -3,9 +3,7 @@ ...@@ -3,9 +3,7 @@
import Vue from 'vue'; import Vue from 'vue';
import boardList from './board_list'; import boardList from './board_list';
import boardBlankState from './board_blank_state'; import boardBlankState from './board_blank_state';
import './board_delete';
require('./board_delete');
require('./board_list');
const Store = gl.issueBoards.BoardsStore; const Store = gl.issueBoards.BoardsStore;
......
require('./issue_card_inner'); import './issue_card_inner';
const Store = gl.issueBoards.BoardsStore; const Store = gl.issueBoards.BoardsStore;
......
...@@ -7,11 +7,9 @@ ...@@ -7,11 +7,9 @@
import Vue from 'vue'; import Vue from 'vue';
import eventHub from '../../sidebar/event_hub'; import eventHub from '../../sidebar/event_hub';
import AssigneeTitle from '../../sidebar/components/assignees/assignee_title'; import AssigneeTitle from '../../sidebar/components/assignees/assignee_title';
import Assignees from '../../sidebar/components/assignees/assignees'; import Assignees from '../../sidebar/components/assignees/assignees';
import './sidebar/remove_issue';
require('./sidebar/remove_issue');
const Store = gl.issueBoards.BoardsStore; const Store = gl.issueBoards.BoardsStore;
...@@ -45,6 +43,12 @@ gl.issueBoards.BoardSidebar = Vue.extend({ ...@@ -45,6 +43,12 @@ gl.issueBoards.BoardSidebar = Vue.extend({
detail: { detail: {
handler () { handler () {
if (this.issue.id !== this.detail.issue.id) { if (this.issue.id !== this.detail.issue.id) {
$('.block.assignee')
.find('input:not(.js-vue)[name="issue[assignee_ids][]"]')
.each((i, el) => {
$(el).remove();
});
$('.js-issue-board-sidebar', this.$el).each((i, el) => { $('.js-issue-board-sidebar', this.$el).each((i, el) => {
$(el).data('glDropdown').clearMenu(); $(el).data('glDropdown').clearMenu();
}); });
...@@ -59,18 +63,6 @@ gl.issueBoards.BoardSidebar = Vue.extend({ ...@@ -59,18 +63,6 @@ gl.issueBoards.BoardSidebar = Vue.extend({
}, },
deep: true deep: true
}, },
issue () {
if (this.showSidebar) {
this.$nextTick(() => {
$('.right-sidebar').getNiceScroll(0).doScrollTop(0, 0);
$('.right-sidebar').getNiceScroll().resize();
});
}
this.issue = this.detail.issue;
this.list = this.detail.list;
},
deep: true
}, },
methods: { methods: {
closeSidebar () { closeSidebar () {
......
import Vue from 'vue'; import Vue from 'vue';
import userAvatarLink from '../../vue_shared/components/user_avatar/user_avatar_link.vue';
import eventHub from '../eventhub'; import eventHub from '../eventhub';
const Store = gl.issueBoards.BoardsStore; const Store = gl.issueBoards.BoardsStore;
...@@ -38,6 +39,9 @@ gl.issueBoards.IssueCardInner = Vue.extend({ ...@@ -38,6 +39,9 @@ gl.issueBoards.IssueCardInner = Vue.extend({
maxCounter: 99, maxCounter: 99,
}; };
}, },
components: {
userAvatarLink,
},
computed: { computed: {
numberOverLimit() { numberOverLimit() {
return this.issue.assignees.length - this.limitBeforeCounter; return this.issue.assignees.length - this.limitBeforeCounter;
...@@ -146,23 +150,16 @@ gl.issueBoards.IssueCardInner = Vue.extend({ ...@@ -146,23 +150,16 @@ gl.issueBoards.IssueCardInner = Vue.extend({
</span> </span>
</h4> </h4>
<div class="card-assignee"> <div class="card-assignee">
<a <user-avatar-link
class="has-tooltip js-no-trigger"
:href="assigneeUrl(assignee)"
:title="assigneeUrlTitle(assignee)"
v-for="(assignee, index) in issue.assignees" v-for="(assignee, index) in issue.assignees"
v-if="shouldRenderAssignee(index)" v-if="shouldRenderAssignee(index)"
data-container="body" class="js-no-trigger"
data-placement="bottom" :link-href="assigneeUrl(assignee)"
> :img-alt="avatarUrlTitle(assignee)"
<img :img-src="assignee.avatar"
class="avatar avatar-inline s20" :tooltip-text="assigneeUrlTitle(assignee)"
:src="assignee.avatar" tooltip-placement="bottom"
width="20" />
height="20"
:alt="avatarUrlTitle(assignee)"
/>
</a>
<span <span
class="avatar-counter has-tooltip" class="avatar-counter has-tooltip"
:title="assigneeCounterTooltip" :title="assigneeCounterTooltip"
......
...@@ -2,8 +2,7 @@ ...@@ -2,8 +2,7 @@
/* global Flash */ /* global Flash */
import Vue from 'vue'; import Vue from 'vue';
import './lists_dropdown';
require('./lists_dropdown');
const ModalStore = gl.issueBoards.ModalStore; const ModalStore = gl.issueBoards.ModalStore;
......
import Vue from 'vue'; import Vue from 'vue';
import modalFilters from './filters'; import modalFilters from './filters';
import './tabs';
require('./tabs');
const ModalStore = gl.issueBoards.ModalStore; const ModalStore = gl.issueBoards.ModalStore;
......
...@@ -3,11 +3,10 @@ ...@@ -3,11 +3,10 @@
import Vue from 'vue'; import Vue from 'vue';
import queryData from '../../utils/query_data'; import queryData from '../../utils/query_data';
import loadingIcon from '../../../vue_shared/components/loading_icon.vue'; import loadingIcon from '../../../vue_shared/components/loading_icon.vue';
import './header';
require('./header'); import './list';
require('./list'); import './footer';
require('./footer'); import './empty_state';
require('./empty_state');
const ModalStore = gl.issueBoards.ModalStore; const ModalStore = gl.issueBoards.ModalStore;
......
import Vue from 'vue'; import Vue from 'vue';
import Visibility from 'visibilityjs'; import Visibility from 'visibilityjs';
import PipelinesTableComponent from '../../vue_shared/components/pipelines_table'; import pipelinesTableComponent from '../../vue_shared/components/pipelines_table';
import PipelinesService from '../../pipelines/services/pipelines_service'; import PipelinesService from '../../pipelines/services/pipelines_service';
import PipelineStore from '../../pipelines/stores/pipelines_store'; import PipelineStore from '../../pipelines/stores/pipelines_store';
import eventHub from '../../pipelines/event_hub'; import eventHub from '../../pipelines/event_hub';
import EmptyState from '../../pipelines/components/empty_state.vue'; import emptyState from '../../pipelines/components/empty_state.vue';
import ErrorState from '../../pipelines/components/error_state.vue'; import errorState from '../../pipelines/components/error_state.vue';
import loadingIcon from '../../vue_shared/components/loading_icon.vue'; import loadingIcon from '../../vue_shared/components/loading_icon.vue';
import '../../lib/utils/common_utils'; import '../../lib/utils/common_utils';
import '../../vue_shared/vue_resource_interceptor'; import '../../vue_shared/vue_resource_interceptor';
...@@ -23,9 +23,9 @@ import Poll from '../../lib/utils/poll'; ...@@ -23,9 +23,9 @@ import Poll from '../../lib/utils/poll';
export default Vue.component('pipelines-table', { export default Vue.component('pipelines-table', {
components: { components: {
'pipelines-table-component': PipelinesTableComponent, pipelinesTableComponent,
'error-state': ErrorState, errorState,
'empty-state': EmptyState, emptyState,
loadingIcon, loadingIcon,
}, },
...@@ -47,6 +47,7 @@ export default Vue.component('pipelines-table', { ...@@ -47,6 +47,7 @@ export default Vue.component('pipelines-table', {
hasError: false, hasError: false,
isMakingRequest: false, isMakingRequest: false,
updateGraphDropdown: false, updateGraphDropdown: false,
hasMadeRequest: false,
}; };
}, },
...@@ -55,9 +56,15 @@ export default Vue.component('pipelines-table', { ...@@ -55,9 +56,15 @@ export default Vue.component('pipelines-table', {
return this.hasError && !this.isLoading; return this.hasError && !this.isLoading;
}, },
/**
* Empty state is only rendered if after the first request we receive no pipelines.
*
* @return {Boolean}
*/
shouldRenderEmptyState() { shouldRenderEmptyState() {
return !this.state.pipelines.length && return !this.state.pipelines.length &&
!this.isLoading && !this.isLoading &&
this.hasMadeRequest &&
!this.hasError; !this.hasError;
}, },
...@@ -94,6 +101,10 @@ export default Vue.component('pipelines-table', { ...@@ -94,6 +101,10 @@ export default Vue.component('pipelines-table', {
if (!Visibility.hidden()) { if (!Visibility.hidden()) {
this.isLoading = true; this.isLoading = true;
this.poll.makeRequest(); this.poll.makeRequest();
} else {
// If tab is not visible we need to make the first request so we don't show the empty
// state without knowing if there are any pipelines
this.fetchPipelines();
} }
Visibility.change(() => { Visibility.change(() => {
...@@ -127,6 +138,8 @@ export default Vue.component('pipelines-table', { ...@@ -127,6 +138,8 @@ export default Vue.component('pipelines-table', {
successCallback(resp) { successCallback(resp) {
const response = resp.json(); const response = resp.json();
this.hasMadeRequest = true;
// depending of the endpoint the response can either bring a `pipelines` key or not. // depending of the endpoint the response can either bring a `pipelines` key or not.
const pipelines = response.pipelines || response; const pipelines = response.pipelines || response;
this.store.storePipelines(pipelines); this.store.storePipelines(pipelines);
......
/* eslint-disable class-methods-use-this, object-shorthand, no-unused-vars, no-use-before-define, no-new, max-len, no-restricted-syntax, guard-for-in, no-continue */ /* eslint-disable class-methods-use-this, object-shorthand, no-unused-vars, no-use-before-define, no-new, max-len, no-restricted-syntax, guard-for-in, no-continue */
require('./lib/utils/common_utils'); import './lib/utils/common_utils';
const gfmRules = { const gfmRules = {
// The filters referenced in lib/banzai/pipeline/gfm_pipeline.rb convert // The filters referenced in lib/banzai/pipeline/gfm_pipeline.rb convert
...@@ -18,12 +18,12 @@ const gfmRules = { ...@@ -18,12 +18,12 @@ const gfmRules = {
}, },
}, },
TaskListFilter: { TaskListFilter: {
'input[type=checkbox].task-list-item-checkbox'(el, text) { 'input[type=checkbox].task-list-item-checkbox'(el) {
return `[${el.checked ? 'x' : ' '}]`; return `[${el.checked ? 'x' : ' '}]`;
}, },
}, },
ReferenceFilter: { ReferenceFilter: {
'.tooltip'(el, text) { '.tooltip'(el) {
return ''; return '';
}, },
'a.gfm:not([data-link=true])'(el, text) { 'a.gfm:not([data-link=true])'(el, text) {
...@@ -39,15 +39,15 @@ const gfmRules = { ...@@ -39,15 +39,15 @@ const gfmRules = {
}, },
}, },
TableOfContentsFilter: { TableOfContentsFilter: {
'ul.section-nav'(el, text) { 'ul.section-nav'(el) {
return '[[_TOC_]]'; return '[[_TOC_]]';
}, },
}, },
EmojiFilter: { EmojiFilter: {
'img.emoji'(el, text) { 'img.emoji'(el) {
return el.getAttribute('alt'); return el.getAttribute('alt');
}, },
'gl-emoji'(el, text) { 'gl-emoji'(el) {
return `:${el.getAttribute('data-name')}:`; return `:${el.getAttribute('data-name')}:`;
}, },
}, },
...@@ -57,13 +57,13 @@ const gfmRules = { ...@@ -57,13 +57,13 @@ const gfmRules = {
}, },
}, },
VideoLinkFilter: { VideoLinkFilter: {
'.video-container'(el, text) { '.video-container'(el) {
const videoEl = el.querySelector('video'); const videoEl = el.querySelector('video');
if (!videoEl) return false; if (!videoEl) return false;
return CopyAsGFM.nodeToGFM(videoEl); return CopyAsGFM.nodeToGFM(videoEl);
}, },
'video'(el, text) { 'video'(el) {
return `![${el.dataset.title}](${el.getAttribute('src')})`; return `![${el.dataset.title}](${el.getAttribute('src')})`;
}, },
}, },
...@@ -74,19 +74,19 @@ const gfmRules = { ...@@ -74,19 +74,19 @@ const gfmRules = {
'code.code.math[data-math-style=inline]'(el, text) { 'code.code.math[data-math-style=inline]'(el, text) {
return `$\`${text}\`$`; return `$\`${text}\`$`;
}, },
'span.katex-display span.katex-mathml'(el, text) { 'span.katex-display span.katex-mathml'(el) {
const mathAnnotation = el.querySelector('annotation[encoding="application/x-tex"]'); const mathAnnotation = el.querySelector('annotation[encoding="application/x-tex"]');
if (!mathAnnotation) return false; if (!mathAnnotation) return false;
return `\`\`\`math\n${CopyAsGFM.nodeToGFM(mathAnnotation)}\n\`\`\``; return `\`\`\`math\n${CopyAsGFM.nodeToGFM(mathAnnotation)}\n\`\`\``;
}, },
'span.katex-mathml'(el, text) { 'span.katex-mathml'(el) {
const mathAnnotation = el.querySelector('annotation[encoding="application/x-tex"]'); const mathAnnotation = el.querySelector('annotation[encoding="application/x-tex"]');
if (!mathAnnotation) return false; if (!mathAnnotation) return false;
return `$\`${CopyAsGFM.nodeToGFM(mathAnnotation)}\`$`; return `$\`${CopyAsGFM.nodeToGFM(mathAnnotation)}\`$`;
}, },
'span.katex-html'(el, text) { 'span.katex-html'(el) {
// We don't want to include the content of this element in the copied text. // We don't want to include the content of this element in the copied text.
return ''; return '';
}, },
...@@ -95,7 +95,7 @@ const gfmRules = { ...@@ -95,7 +95,7 @@ const gfmRules = {
}, },
}, },
SanitizationFilter: { SanitizationFilter: {
'a[name]:not([href]):empty'(el, text) { 'a[name]:not([href]):empty'(el) {
return el.outerHTML; return el.outerHTML;
}, },
'dl'(el, text) { 'dl'(el, text) {
...@@ -143,7 +143,7 @@ const gfmRules = { ...@@ -143,7 +143,7 @@ const gfmRules = {
}, },
}, },
MarkdownFilter: { MarkdownFilter: {
'br'(el, text) { 'br'(el) {
// Two spaces at the end of a line are turned into a BR // Two spaces at the end of a line are turned into a BR
return ' '; return ' ';
}, },
...@@ -162,7 +162,7 @@ const gfmRules = { ...@@ -162,7 +162,7 @@ const gfmRules = {
'blockquote'(el, text) { 'blockquote'(el, text) {
return text.trim().split('\n').map(s => `> ${s}`.trim()).join('\n'); return text.trim().split('\n').map(s => `> ${s}`.trim()).join('\n');
}, },
'img'(el, text) { 'img'(el) {
return `![${el.getAttribute('alt')}](${el.getAttribute('src')})`; return `![${el.getAttribute('alt')}](${el.getAttribute('src')})`;
}, },
'a.anchor'(el, text) { 'a.anchor'(el, text) {
...@@ -222,10 +222,10 @@ const gfmRules = { ...@@ -222,10 +222,10 @@ const gfmRules = {
'sup'(el, text) { 'sup'(el, text) {
return `^${text}`; return `^${text}`;
}, },
'hr'(el, text) { 'hr'(el) {
return '-----'; return '-----';
}, },
'table'(el, text) { 'table'(el) {
const theadEl = el.querySelector('thead'); const theadEl = el.querySelector('thead');
const tbodyEl = el.querySelector('tbody'); const tbodyEl = el.querySelector('tbody');
if (!theadEl || !tbodyEl) return false; if (!theadEl || !tbodyEl) return false;
...@@ -233,11 +233,11 @@ const gfmRules = { ...@@ -233,11 +233,11 @@ const gfmRules = {
const theadText = CopyAsGFM.nodeToGFM(theadEl); const theadText = CopyAsGFM.nodeToGFM(theadEl);
const tbodyText = CopyAsGFM.nodeToGFM(tbodyEl); const tbodyText = CopyAsGFM.nodeToGFM(tbodyEl);
return theadText + tbodyText; return [theadText, tbodyText].join('\n');
}, },
'thead'(el, text) { 'thead'(el, text) {
const cells = _.map(el.querySelectorAll('th'), (cell) => { const cells = _.map(el.querySelectorAll('th'), (cell) => {
let chars = CopyAsGFM.nodeToGFM(cell).trim().length + 2; let chars = CopyAsGFM.nodeToGFM(cell).length + 2;
let before = ''; let before = '';
let after = ''; let after = '';
...@@ -262,10 +262,15 @@ const gfmRules = { ...@@ -262,10 +262,15 @@ const gfmRules = {
return before + middle + after; return before + middle + after;
}); });
return `${text}|${cells.join('|')}|`; const separatorRow = `|${cells.join('|')}|`;
return [text, separatorRow].join('\n');
}, },
'tr'(el, text) { 'tr'(el) {
const cells = _.map(el.querySelectorAll('td, th'), cell => CopyAsGFM.nodeToGFM(cell).trim()); const cellEls = el.querySelectorAll('td, th');
if (cellEls.length === 0) return false;
const cells = _.map(cellEls, cell => CopyAsGFM.nodeToGFM(cell));
return `| ${cells.join(' | ')} |`; return `| ${cells.join(' | ')} |`;
}, },
}, },
...@@ -273,12 +278,12 @@ const gfmRules = { ...@@ -273,12 +278,12 @@ const gfmRules = {
class CopyAsGFM { class CopyAsGFM {
constructor() { constructor() {
$(document).on('copy', '.md, .wiki', (e) => { this.copyAsGFM(e, CopyAsGFM.transformGFMSelection); }); $(document).on('copy', '.md, .wiki', (e) => { CopyAsGFM.copyAsGFM(e, CopyAsGFM.transformGFMSelection); });
$(document).on('copy', 'pre.code.highlight, .diff-content .line_content', (e) => { this.copyAsGFM(e, CopyAsGFM.transformCodeSelection); }); $(document).on('copy', 'pre.code.highlight, .diff-content .line_content', (e) => { CopyAsGFM.copyAsGFM(e, CopyAsGFM.transformCodeSelection); });
$(document).on('paste', '.js-gfm-input', this.pasteGFM.bind(this)); $(document).on('paste', '.js-gfm-input', CopyAsGFM.pasteGFM);
} }
copyAsGFM(e, transformer) { static copyAsGFM(e, transformer) {
const clipboardData = e.originalEvent.clipboardData; const clipboardData = e.originalEvent.clipboardData;
if (!clipboardData) return; if (!clipboardData) return;
...@@ -292,26 +297,59 @@ class CopyAsGFM { ...@@ -292,26 +297,59 @@ class CopyAsGFM {
e.stopPropagation(); e.stopPropagation();
clipboardData.setData('text/plain', el.textContent); clipboardData.setData('text/plain', el.textContent);
clipboardData.setData('text/x-gfm', CopyAsGFM.nodeToGFM(el)); clipboardData.setData('text/x-gfm', this.nodeToGFM(el));
} }
pasteGFM(e) { static pasteGFM(e) {
const clipboardData = e.originalEvent.clipboardData; const clipboardData = e.originalEvent.clipboardData;
if (!clipboardData) return; if (!clipboardData) return;
const text = clipboardData.getData('text/plain');
const gfm = clipboardData.getData('text/x-gfm'); const gfm = clipboardData.getData('text/x-gfm');
if (!gfm) return; if (!gfm) return;
e.preventDefault(); e.preventDefault();
window.gl.utils.insertText(e.target, gfm); window.gl.utils.insertText(e.target, (textBefore, textAfter) => {
// If the text before the cursor contains an odd number of backticks,
// we are either inside an inline code span that starts with 1 backtick
// or a code block that starts with 3 backticks.
// This logic still holds when there are one or more _closed_ code spans
// or blocks that will have 2 or 6 backticks.
// This will break down when the actual code block contains an uneven
// number of backticks, but this is a rare edge case.
const backtickMatch = textBefore.match(/`/g);
const insideCodeBlock = backtickMatch && (backtickMatch.length % 2) === 1;
if (insideCodeBlock) {
return text;
}
return gfm;
});
} }
static transformGFMSelection(documentFragment) { static transformGFMSelection(documentFragment) {
// If the documentFragment contains more than just Markdown, don't copy as GFM. const gfmEls = documentFragment.querySelectorAll('.md, .wiki');
if (documentFragment.querySelector('.md, .wiki')) return null; switch (gfmEls.length) {
case 0: {
return documentFragment;
}
case 1: {
return gfmEls[0];
}
default: {
const allGfmEl = document.createElement('div');
for (let i = 0; i < gfmEls.length; i += 1) {
const lineEl = gfmEls[i];
allGfmEl.appendChild(lineEl);
allGfmEl.appendChild(document.createTextNode('\n\n'));
}
return documentFragment; return allGfmEl;
}
}
} }
static transformCodeSelection(documentFragment) { static transformCodeSelection(documentFragment) {
...@@ -343,7 +381,7 @@ class CopyAsGFM { ...@@ -343,7 +381,7 @@ class CopyAsGFM {
return codeEl; return codeEl;
} }
static nodeToGFM(node) { static nodeToGFM(node, respectWhitespaceParam = false) {
if (node.nodeType === Node.COMMENT_NODE) { if (node.nodeType === Node.COMMENT_NODE) {
return ''; return '';
} }
...@@ -352,7 +390,9 @@ class CopyAsGFM { ...@@ -352,7 +390,9 @@ class CopyAsGFM {
return node.textContent; return node.textContent;
} }
const text = this.innerGFM(node); const respectWhitespace = respectWhitespaceParam || (node.nodeName === 'PRE' || node.nodeName === 'CODE');
const text = this.innerGFM(node, respectWhitespace);
if (node.nodeType === Node.DOCUMENT_FRAGMENT_NODE) { if (node.nodeType === Node.DOCUMENT_FRAGMENT_NODE) {
return text; return text;
...@@ -366,7 +406,17 @@ class CopyAsGFM { ...@@ -366,7 +406,17 @@ class CopyAsGFM {
if (!window.gl.utils.nodeMatchesSelector(node, selector)) continue; if (!window.gl.utils.nodeMatchesSelector(node, selector)) continue;
const result = func(node, text); let result;
if (func.length === 2) {
// if `func` takes 2 arguments, it depends on text.
// if there is no text, we don't need to generate GFM for this node.
if (text.length === 0) continue;
result = func(node, text);
} else {
result = func(node);
}
if (result === false) continue; if (result === false) continue;
return result; return result;
...@@ -376,7 +426,7 @@ class CopyAsGFM { ...@@ -376,7 +426,7 @@ class CopyAsGFM {
return text; return text;
} }
static innerGFM(parentNode) { static innerGFM(parentNode, respectWhitespace = false) {
const nodes = parentNode.childNodes; const nodes = parentNode.childNodes;
const clonedParentNode = parentNode.cloneNode(true); const clonedParentNode = parentNode.cloneNode(true);
...@@ -386,13 +436,19 @@ class CopyAsGFM { ...@@ -386,13 +436,19 @@ class CopyAsGFM {
const node = nodes[i]; const node = nodes[i];
const clonedNode = clonedNodes[i]; const clonedNode = clonedNodes[i];
const text = this.nodeToGFM(node); const text = this.nodeToGFM(node, respectWhitespace);
// `clonedNode.replaceWith(text)` is not yet widely supported // `clonedNode.replaceWith(text)` is not yet widely supported
clonedNode.parentNode.replaceChild(document.createTextNode(text), clonedNode); clonedNode.parentNode.replaceChild(document.createTextNode(text), clonedNode);
} }
return clonedParentNode.innerText || clonedParentNode.textContent; let nodeText = clonedParentNode.innerText || clonedParentNode.textContent;
if (!respectWhitespace) {
nodeText = nodeText.trim();
}
return nodeText;
} }
} }
......
/* eslint-disable func-names, space-before-function-paren, prefer-arrow-callback, comma-dangle, prefer-template, quotes, no-param-reassign, wrap-iife, max-len */ /* eslint-disable func-names, space-before-function-paren, prefer-arrow-callback, comma-dangle, prefer-template, quotes, no-param-reassign, wrap-iife, max-len */
/* global Api */ import Api from './api';
class CreateLabelDropdown { class CreateLabelDropdown {
constructor ($el, namespacePath, projectPath) { constructor ($el, namespacePath, projectPath) {
......
/* eslint-disable no-param-reassign */ /* eslint-disable no-param-reassign */
import Vue from 'vue'; import Vue from 'vue';
import userAvatarImage from '../../vue_shared/components/user_avatar/user_avatar_image.vue';
const global = window.gl || (window.gl = {}); const global = window.gl || (window.gl = {});
global.cycleAnalytics = global.cycleAnalytics || {}; global.cycleAnalytics = global.cycleAnalytics || {};
...@@ -10,6 +11,9 @@ global.cycleAnalytics.StageCodeComponent = Vue.extend({ ...@@ -10,6 +11,9 @@ global.cycleAnalytics.StageCodeComponent = Vue.extend({
items: Array, items: Array,
stage: Object, stage: Object,
}, },
components: {
userAvatarImage,
},
template: ` template: `
<div> <div>
<div class="events-description"> <div class="events-description">
...@@ -19,7 +23,8 @@ global.cycleAnalytics.StageCodeComponent = Vue.extend({ ...@@ -19,7 +23,8 @@ global.cycleAnalytics.StageCodeComponent = Vue.extend({
<ul class="stage-event-list"> <ul class="stage-event-list">
<li v-for="mergeRequest in items" class="stage-event-item"> <li v-for="mergeRequest in items" class="stage-event-item">
<div class="item-details"> <div class="item-details">
<img class="avatar" :src="mergeRequest.author.avatarUrl"> <!-- FIXME: Pass an alt attribute here for accessibility -->
<user-avatar-image :img-src="mergeRequest.author.avatarUrl"/>
<h5 class="item-title merge-merquest-title"> <h5 class="item-title merge-merquest-title">
<a :href="mergeRequest.url"> <a :href="mergeRequest.url">
{{ mergeRequest.title }} {{ mergeRequest.title }}
...@@ -28,11 +33,11 @@ global.cycleAnalytics.StageCodeComponent = Vue.extend({ ...@@ -28,11 +33,11 @@ global.cycleAnalytics.StageCodeComponent = Vue.extend({
<a :href="mergeRequest.url" class="issue-link">!{{ mergeRequest.iid }}</a> <a :href="mergeRequest.url" class="issue-link">!{{ mergeRequest.iid }}</a>
&middot; &middot;
<span> <span>
{{ __('OpenedNDaysAgo|Opened') }} {{ s__('OpenedNDaysAgo|Opened') }}
<a :href="mergeRequest.url" class="issue-date">{{ mergeRequest.createdAt }}</a> <a :href="mergeRequest.url" class="issue-date">{{ mergeRequest.createdAt }}</a>
</span> </span>
<span> <span>
{{ __('ByAuthor|by') }} {{ s__('ByAuthor|by') }}
<a :href="mergeRequest.author.webUrl" class="issue-author-link">{{ mergeRequest.author.name }}</a> <a :href="mergeRequest.author.webUrl" class="issue-author-link">{{ mergeRequest.author.name }}</a>
</span> </span>
</div> </div>
......
/* eslint-disable no-param-reassign */ /* eslint-disable no-param-reassign */
import Vue from 'vue'; import Vue from 'vue';
import userAvatarImage from '../../vue_shared/components/user_avatar/user_avatar_image.vue';
const global = window.gl || (window.gl = {}); const global = window.gl || (window.gl = {});
global.cycleAnalytics = global.cycleAnalytics || {}; global.cycleAnalytics = global.cycleAnalytics || {};
...@@ -10,6 +10,9 @@ global.cycleAnalytics.StageIssueComponent = Vue.extend({ ...@@ -10,6 +10,9 @@ global.cycleAnalytics.StageIssueComponent = Vue.extend({
items: Array, items: Array,
stage: Object, stage: Object,
}, },
components: {
userAvatarImage,
},
template: ` template: `
<div> <div>
<div class="events-description"> <div class="events-description">
...@@ -19,7 +22,8 @@ global.cycleAnalytics.StageIssueComponent = Vue.extend({ ...@@ -19,7 +22,8 @@ global.cycleAnalytics.StageIssueComponent = Vue.extend({
<ul class="stage-event-list"> <ul class="stage-event-list">
<li v-for="issue in items" class="stage-event-item"> <li v-for="issue in items" class="stage-event-item">
<div class="item-details"> <div class="item-details">
<img class="avatar" :src="issue.author.avatarUrl"> <!-- FIXME: Pass an alt attribute here for accessibility -->
<user-avatar-image :img-src="issue.author.avatarUrl"/>
<h5 class="item-title issue-title"> <h5 class="item-title issue-title">
<a class="issue-title" :href="issue.url"> <a class="issue-title" :href="issue.url">
{{ issue.title }} {{ issue.title }}
...@@ -28,11 +32,11 @@ global.cycleAnalytics.StageIssueComponent = Vue.extend({ ...@@ -28,11 +32,11 @@ global.cycleAnalytics.StageIssueComponent = Vue.extend({
<a :href="issue.url" class="issue-link">#{{ issue.iid }}</a> <a :href="issue.url" class="issue-link">#{{ issue.iid }}</a>
&middot; &middot;
<span> <span>
{{ __('OpenedNDaysAgo|Opened') }} {{ s__('OpenedNDaysAgo|Opened') }}
<a :href="issue.url" class="issue-date">{{ issue.createdAt }}</a> <a :href="issue.url" class="issue-date">{{ issue.createdAt }}</a>
</span> </span>
<span> <span>
{{ __('ByAuthor|by') }} {{ s__('ByAuthor|by') }}
<a :href="issue.author.webUrl" class="issue-author-link"> <a :href="issue.author.webUrl" class="issue-author-link">
{{ issue.author.name }} {{ issue.author.name }}
</a> </a>
......
/* eslint-disable no-param-reassign */ /* eslint-disable no-param-reassign */
import Vue from 'vue'; import Vue from 'vue';
import userAvatarImage from '../../vue_shared/components/user_avatar/user_avatar_image.vue';
import iconCommit from '../svg/icon_commit.svg'; import iconCommit from '../svg/icon_commit.svg';
const global = window.gl || (window.gl = {}); const global = window.gl || (window.gl = {});
...@@ -10,11 +11,12 @@ global.cycleAnalytics.StagePlanComponent = Vue.extend({ ...@@ -10,11 +11,12 @@ global.cycleAnalytics.StagePlanComponent = Vue.extend({
items: Array, items: Array,
stage: Object, stage: Object,
}, },
components: {
userAvatarImage,
},
data() { data() {
return { iconCommit }; return { iconCommit };
}, },
template: ` template: `
<div> <div>
<div class="events-description"> <div class="events-description">
...@@ -24,17 +26,18 @@ global.cycleAnalytics.StagePlanComponent = Vue.extend({ ...@@ -24,17 +26,18 @@ global.cycleAnalytics.StagePlanComponent = Vue.extend({
<ul class="stage-event-list"> <ul class="stage-event-list">
<li v-for="commit in items" class="stage-event-item"> <li v-for="commit in items" class="stage-event-item">
<div class="item-details item-conmmit-component"> <div class="item-details item-conmmit-component">
<img class="avatar" :src="commit.author.avatarUrl"> <!-- FIXME: Pass an alt attribute here for accessibility -->
<user-avatar-image :img-src="commit.author.avatarUrl"/>
<h5 class="item-title commit-title"> <h5 class="item-title commit-title">
<a :href="commit.commitUrl"> <a :href="commit.commitUrl">
{{ commit.title }} {{ commit.title }}
</a> </a>
</h5> </h5>
<span> <span>
{{ __('FirstPushedBy|First') }} {{ s__('FirstPushedBy|First') }}
<span class="commit-icon">${iconCommit}</span> <span class="commit-icon">${iconCommit}</span>
<a :href="commit.commitUrl" class="commit-hash-link commit-sha">{{ commit.shortSha }}</a> <a :href="commit.commitUrl" class="commit-hash-link commit-sha">{{ commit.shortSha }}</a>
{{ __('FirstPushedBy|pushed by') }} {{ s__('FirstPushedBy|pushed by') }}
<a :href="commit.author.webUrl" class="commit-author-link"> <a :href="commit.author.webUrl" class="commit-author-link">
{{ commit.author.name }} {{ commit.author.name }}
</a> </a>
......
/* eslint-disable no-param-reassign */ /* eslint-disable no-param-reassign */
import Vue from 'vue'; import Vue from 'vue';
import userAvatarImage from '../../vue_shared/components/user_avatar/user_avatar_image.vue';
const global = window.gl || (window.gl = {}); const global = window.gl || (window.gl = {});
global.cycleAnalytics = global.cycleAnalytics || {}; global.cycleAnalytics = global.cycleAnalytics || {};
...@@ -10,6 +10,9 @@ global.cycleAnalytics.StageProductionComponent = Vue.extend({ ...@@ -10,6 +10,9 @@ global.cycleAnalytics.StageProductionComponent = Vue.extend({
items: Array, items: Array,
stage: Object, stage: Object,
}, },
components: {
userAvatarImage,
},
template: ` template: `
<div> <div>
<div class="events-description"> <div class="events-description">
...@@ -19,7 +22,8 @@ global.cycleAnalytics.StageProductionComponent = Vue.extend({ ...@@ -19,7 +22,8 @@ global.cycleAnalytics.StageProductionComponent = Vue.extend({
<ul class="stage-event-list"> <ul class="stage-event-list">
<li v-for="issue in items" class="stage-event-item"> <li v-for="issue in items" class="stage-event-item">
<div class="item-details"> <div class="item-details">
<img class="avatar" :src="issue.author.avatarUrl"> <!-- FIXME: Pass an alt attribute here for accessibility -->
<user-avatar-image :img-src="issue.author.avatarUrl"/>
<h5 class="item-title issue-title"> <h5 class="item-title issue-title">
<a class="issue-title" :href="issue.url"> <a class="issue-title" :href="issue.url">
{{ issue.title }} {{ issue.title }}
...@@ -28,11 +32,11 @@ global.cycleAnalytics.StageProductionComponent = Vue.extend({ ...@@ -28,11 +32,11 @@ global.cycleAnalytics.StageProductionComponent = Vue.extend({
<a :href="issue.url" class="issue-link">#{{ issue.iid }}</a> <a :href="issue.url" class="issue-link">#{{ issue.iid }}</a>
&middot; &middot;
<span> <span>
{{ __('OpenedNDaysAgo|Opened') }} {{ s__('OpenedNDaysAgo|Opened') }}
<a :href="issue.url" class="issue-date">{{ issue.createdAt }}</a> <a :href="issue.url" class="issue-date">{{ issue.createdAt }}</a>
</span> </span>
<span> <span>
{{ __('ByAuthor|by') }} {{ s__('ByAuthor|by') }}
<a :href="issue.author.webUrl" class="issue-author-link"> <a :href="issue.author.webUrl" class="issue-author-link">
{{ issue.author.name }} {{ issue.author.name }}
</a> </a>
......
/* eslint-disable no-param-reassign */ /* eslint-disable no-param-reassign */
import Vue from 'vue'; import Vue from 'vue';
import userAvatarImage from '../../vue_shared/components/user_avatar/user_avatar_image.vue';
const global = window.gl || (window.gl = {}); const global = window.gl || (window.gl = {});
global.cycleAnalytics = global.cycleAnalytics || {}; global.cycleAnalytics = global.cycleAnalytics || {};
...@@ -10,6 +10,9 @@ global.cycleAnalytics.StageReviewComponent = Vue.extend({ ...@@ -10,6 +10,9 @@ global.cycleAnalytics.StageReviewComponent = Vue.extend({
items: Array, items: Array,
stage: Object, stage: Object,
}, },
components: {
userAvatarImage,
},
template: ` template: `
<div> <div>
<div class="events-description"> <div class="events-description">
...@@ -19,7 +22,8 @@ global.cycleAnalytics.StageReviewComponent = Vue.extend({ ...@@ -19,7 +22,8 @@ global.cycleAnalytics.StageReviewComponent = Vue.extend({
<ul class="stage-event-list"> <ul class="stage-event-list">
<li v-for="mergeRequest in items" class="stage-event-item"> <li v-for="mergeRequest in items" class="stage-event-item">
<div class="item-details"> <div class="item-details">
<img class="avatar" :src="mergeRequest.author.avatarUrl"> <!-- FIXME: Pass an alt attribute here for accessibility -->
<user-avatar-image :img-src="mergeRequest.author.avatarUrl"/>
<h5 class="item-title merge-merquest-title"> <h5 class="item-title merge-merquest-title">
<a :href="mergeRequest.url"> <a :href="mergeRequest.url">
{{ mergeRequest.title }} {{ mergeRequest.title }}
...@@ -28,11 +32,11 @@ global.cycleAnalytics.StageReviewComponent = Vue.extend({ ...@@ -28,11 +32,11 @@ global.cycleAnalytics.StageReviewComponent = Vue.extend({
<a :href="mergeRequest.url" class="issue-link">!{{ mergeRequest.iid }}</a> <a :href="mergeRequest.url" class="issue-link">!{{ mergeRequest.iid }}</a>
&middot; &middot;
<span> <span>
{{ __('OpenedNDaysAgo|Opened') }} {{ s__('OpenedNDaysAgo|Opened') }}
<a :href="mergeRequest.url" class="issue-date">{{ mergeRequest.createdAt }}</a> <a :href="mergeRequest.url" class="issue-date">{{ mergeRequest.createdAt }}</a>
</span> </span>
<span> <span>
{{ __('ByAuthor|by') }} {{ s__('ByAuthor|by') }}
<a :href="mergeRequest.author.webUrl" class="issue-author-link">{{ mergeRequest.author.name }}</a> <a :href="mergeRequest.author.webUrl" class="issue-author-link">{{ mergeRequest.author.name }}</a>
</span> </span>
<template v-if="mergeRequest.state === 'closed'"> <template v-if="mergeRequest.state === 'closed'">
......
/* eslint-disable no-param-reassign */ /* eslint-disable no-param-reassign */
import Vue from 'vue'; import Vue from 'vue';
import userAvatarImage from '../../vue_shared/components/user_avatar/user_avatar_image.vue';
import iconBranch from '../svg/icon_branch.svg'; import iconBranch from '../svg/icon_branch.svg';
const global = window.gl || (window.gl = {}); const global = window.gl || (window.gl = {});
...@@ -13,6 +14,9 @@ global.cycleAnalytics.StageStagingComponent = Vue.extend({ ...@@ -13,6 +14,9 @@ global.cycleAnalytics.StageStagingComponent = Vue.extend({
data() { data() {
return { iconBranch }; return { iconBranch };
}, },
components: {
userAvatarImage,
},
template: ` template: `
<div> <div>
<div class="events-description"> <div class="events-description">
...@@ -22,7 +26,8 @@ global.cycleAnalytics.StageStagingComponent = Vue.extend({ ...@@ -22,7 +26,8 @@ global.cycleAnalytics.StageStagingComponent = Vue.extend({
<ul class="stage-event-list"> <ul class="stage-event-list">
<li v-for="build in items" class="stage-event-item item-build-component"> <li v-for="build in items" class="stage-event-item item-build-component">
<div class="item-details"> <div class="item-details">
<img class="avatar" :src="build.author.avatarUrl"> <!-- FIXME: Pass an alt attribute here for accessibility -->
<user-avatar-image :img-src="build.author.avatarUrl"/>
<h5 class="item-title"> <h5 class="item-title">
<a :href="build.url" class="pipeline-id">#{{ build.id }}</a> <a :href="build.url" class="pipeline-id">#{{ build.id }}</a>
<i class="fa fa-code-fork"></i> <i class="fa fa-code-fork"></i>
...@@ -32,7 +37,7 @@ global.cycleAnalytics.StageStagingComponent = Vue.extend({ ...@@ -32,7 +37,7 @@ global.cycleAnalytics.StageStagingComponent = Vue.extend({
</h5> </h5>
<span> <span>
<a :href="build.url" class="build-date">{{ build.date }}</a> <a :href="build.url" class="build-date">{{ build.date }}</a>
{{ __('ByAuthor|by') }} {{ s__('ByAuthor|by') }}
<a :href="build.author.webUrl" class="issue-author-link"> <a :href="build.author.webUrl" class="issue-author-link">
{{ build.author.name }} {{ build.author.name }}
</a> </a>
......
...@@ -4,18 +4,16 @@ import Vue from 'vue'; ...@@ -4,18 +4,16 @@ import Vue from 'vue';
import Cookies from 'js-cookie'; import Cookies from 'js-cookie';
import Translate from '../vue_shared/translate'; import Translate from '../vue_shared/translate';
import LimitWarningComponent from './components/limit_warning_component'; import LimitWarningComponent from './components/limit_warning_component';
import './components/stage_code_component';
require('./components/stage_code_component'); import './components/stage_issue_component';
require('./components/stage_issue_component'); import './components/stage_plan_component';
require('./components/stage_plan_component'); import './components/stage_production_component';
require('./components/stage_production_component'); import './components/stage_review_component';
require('./components/stage_review_component'); import './components/stage_staging_component';
require('./components/stage_staging_component'); import './components/stage_test_component';
require('./components/stage_test_component'); import './components/total_time_component';
require('./components/total_time_component'); import './cycle_analytics_service';
require('./cycle_analytics_service'); import './cycle_analytics_store';
require('./cycle_analytics_store');
require('./default_event_objects');
Vue.use(Translate); Vue.use(Translate);
......
/* eslint-disable no-param-reassign */ /* eslint-disable no-param-reassign */
import { __ } from '../locale';
require('../lib/utils/text_utility'); import { __ } from '../locale';
const DEFAULT_EVENT_OBJECTS = require('./default_event_objects'); import '../lib/utils/text_utility';
import DEFAULT_EVENT_OBJECTS from './default_event_objects';
const global = window.gl || (window.gl = {}); const global = window.gl || (window.gl = {});
global.cycleAnalytics = global.cycleAnalytics || {}; global.cycleAnalytics = global.cycleAnalytics || {};
......
module.exports = { export default {
issue: { issue: {
created_at: '', created_at: '',
url: '', url: '',
......
/* eslint-disable class-methods-use-this */ /* eslint-disable class-methods-use-this */
require('./lib/utils/url_utility'); import './lib/utils/url_utility';
const UNFOLD_COUNT = 20; const UNFOLD_COUNT = 20;
let isBound = false; let isBound = false;
......
...@@ -3,6 +3,7 @@ ...@@ -3,6 +3,7 @@
import Vue from 'vue'; import Vue from 'vue';
import collapseIcon from '../icons/collapse_icon.svg'; import collapseIcon from '../icons/collapse_icon.svg';
import userAvatarImage from '../../vue_shared/components/user_avatar/user_avatar_image.vue';
const DiffNoteAvatars = Vue.extend({ const DiffNoteAvatars = Vue.extend({
props: ['discussionId'], props: ['discussionId'],
...@@ -15,22 +16,24 @@ const DiffNoteAvatars = Vue.extend({ ...@@ -15,22 +16,24 @@ const DiffNoteAvatars = Vue.extend({
collapseIcon, collapseIcon,
}; };
}, },
components: {
userAvatarImage,
},
template: ` template: `
<div class="diff-comment-avatar-holders" <div class="diff-comment-avatar-holders"
v-show="notesCount !== 0"> v-show="notesCount !== 0">
<div v-if="!isVisible"> <div v-if="!isVisible">
<img v-for="note in notesSubset" <!-- FIXME: Pass an alt attribute here for accessibility -->
class="avatar diff-comment-avatar has-tooltip js-diff-comment-avatar" <user-avatar-image
width="19" v-for="note in notesSubset"
height="19" class="diff-comment-avatar js-diff-comment-avatar"
role="button" @click.native="clickedAvatar($event)"
data-container="body" :img-src="note.authorAvatar"
data-placement="top" :tooltip-text="getTooltipText(note)"
data-html="true"
:data-line-type="lineType" :data-line-type="lineType"
:title="note.authorName + ': ' + note.noteTruncated" :size="19"
:src="note.authorAvatar" data-html="true"
@click="clickedAvatar($event)" /> />
<span v-if="notesCount > shownAvatars" <span v-if="notesCount > shownAvatars"
class="diff-comments-more-count has-tooltip js-diff-comment-avatar" class="diff-comments-more-count has-tooltip js-diff-comment-avatar"
data-container="body" data-container="body"
...@@ -120,7 +123,7 @@ const DiffNoteAvatars = Vue.extend({ ...@@ -120,7 +123,7 @@ const DiffNoteAvatars = Vue.extend({
}, },
methods: { methods: {
clickedAvatar(e) { clickedAvatar(e) {
notes.addDiffNote(e); notes.onAddDiffNote(e);
// Toggle the active state of the toggle all button // Toggle the active state of the toggle all button
this.toggleDiscussionsToggleState(); this.toggleDiscussionsToggleState();
...@@ -150,6 +153,9 @@ const DiffNoteAvatars = Vue.extend({ ...@@ -150,6 +153,9 @@ const DiffNoteAvatars = Vue.extend({
setDiscussionVisible() { setDiscussionVisible() {
this.isVisible = $(`.diffs .notes[data-discussion-id="${this.discussion.id}"]`).is(':visible'); this.isVisible = $(`.diffs .notes[data-discussion-id="${this.discussion.id}"]`).is(':visible');
}, },
getTooltipText(note) {
return `${note.authorName}: ${note.noteTruncated}`;
},
}, },
}); });
......
...@@ -2,19 +2,18 @@ ...@@ -2,19 +2,18 @@
/* global ResolveCount */ /* global ResolveCount */
import Vue from 'vue'; import Vue from 'vue';
import './models/discussion';
require('./models/discussion'); import './models/note';
require('./models/note'); import './stores/comments';
require('./stores/comments'); import './services/resolve';
require('./services/resolve'); import './mixins/discussion';
require('./mixins/discussion'); import './components/comment_resolve_btn';
require('./components/comment_resolve_btn'); import './components/jump_to_discussion';
require('./components/jump_to_discussion'); import './components/resolve_btn';
require('./components/resolve_btn'); import './components/resolve_count';
require('./components/resolve_count'); import './components/resolve_discussion_btn';
require('./components/resolve_discussion_btn'); import './components/diff_note_avatars';
require('./components/diff_note_avatars'); import './components/new_issue_for_discussion';
require('./components/new_issue_for_discussion');
$(() => { $(() => {
const projectPath = document.querySelector('.merge-request').dataset.projectPath; const projectPath = document.querySelector('.merge-request').dataset.projectPath;
...@@ -65,4 +64,6 @@ $(() => { ...@@ -65,4 +64,6 @@ $(() => {
'resolve-count': ResolveCount 'resolve-count': ResolveCount
} }
}); });
$(window).trigger('resize.nav');
}); });
...@@ -3,11 +3,7 @@ ...@@ -3,11 +3,7 @@
/* global CommentsStore */ /* global CommentsStore */
import Vue from 'vue'; import Vue from 'vue';
import VueResource from 'vue-resource'; import '../../vue_shared/vue_resource_interceptor';
require('../../vue_shared/vue_resource_interceptor');
Vue.use(VueResource);
window.gl = window.gl || {}; window.gl = window.gl || {};
......
...@@ -34,13 +34,13 @@ ...@@ -34,13 +34,13 @@
/* global ShortcutsWiki */ /* global ShortcutsWiki */
import Issue from './issue'; import Issue from './issue';
import BindInOut from './behaviors/bind_in_out'; import BindInOut from './behaviors/bind_in_out';
import DeleteModal from './branches/branches_delete_modal'; import DeleteModal from './branches/branches_delete_modal';
import Group from './group'; import Group from './group';
import GroupName from './group_name'; import GroupName from './group_name';
import GroupsList from './groups_list'; import GroupsList from './groups_list';
import ProjectsList from './projects_list'; import ProjectsList from './projects_list';
import setupProjectEdit from './project_edit';
import MiniPipelineGraph from './mini_pipeline_graph_dropdown'; import MiniPipelineGraph from './mini_pipeline_graph_dropdown';
import BlobLinePermalinkUpdater from './blob/blob_line_permalink_updater'; import BlobLinePermalinkUpdater from './blob/blob_line_permalink_updater';
import Landing from './landing'; import Landing from './landing';
...@@ -52,8 +52,9 @@ import Pipelines from './pipelines'; ...@@ -52,8 +52,9 @@ import Pipelines from './pipelines';
import BlobViewer from './blob/viewer/index'; import BlobViewer from './blob/viewer/index';
import AutoWidthDropdownSelect from './issuable/auto_width_dropdown_select'; import AutoWidthDropdownSelect from './issuable/auto_width_dropdown_select';
import UsersSelect from './users_select'; import UsersSelect from './users_select';
import RefSelectDropdown from './ref_select_dropdown';
const ShortcutsBlob = require('./shortcuts_blob'); import GfmAutoComplete from './gfm_auto_complete';
import ShortcutsBlob from './shortcuts_blob';
(function() { (function() {
var Dispatcher; var Dispatcher;
...@@ -78,6 +79,8 @@ const ShortcutsBlob = require('./shortcuts_blob'); ...@@ -78,6 +79,8 @@ const ShortcutsBlob = require('./shortcuts_blob');
path = page.split(':'); path = page.split(':');
shortcut_handler = null; shortcut_handler = null;
new GfmAutoComplete(gl.GfmAutoComplete && gl.GfmAutoComplete.dataSources).setup();
function initBlob() { function initBlob() {
new LineHighlighter(); new LineHighlighter();
...@@ -212,6 +215,7 @@ const ShortcutsBlob = require('./shortcuts_blob'); ...@@ -212,6 +215,7 @@ const ShortcutsBlob = require('./shortcuts_blob');
case 'projects:tags:new': case 'projects:tags:new':
new ZenMode(); new ZenMode();
new gl.GLForm($('.tag-form')); new gl.GLForm($('.tag-form'));
new RefSelectDropdown($('.js-branch-select'), window.gl.availableRefs);
break; break;
case 'projects:releases:edit': case 'projects:releases:edit':
new ZenMode(); new ZenMode();
...@@ -256,9 +260,14 @@ const ShortcutsBlob = require('./shortcuts_blob'); ...@@ -256,9 +260,14 @@ const ShortcutsBlob = require('./shortcuts_blob');
new NotificationsForm(); new NotificationsForm();
if ($('#tree-slider').length) { if ($('#tree-slider').length) {
new TreeView(); new TreeView();
}
if ($('.blob-viewer').length) {
new BlobViewer(); new BlobViewer();
} }
break; break;
case 'projects:edit':
setupProjectEdit();
break;
case 'projects:pipelines:builds': case 'projects:pipelines:builds':
case 'projects:pipelines:failures': case 'projects:pipelines:failures':
case 'projects:pipelines:show': case 'projects:pipelines:show':
......
/* eslint-disable */ /* eslint-disable */
import AjaxCache from '~/lib/utils/ajax_cache';
const Ajax = { const Ajax = {
_loadUrlData: function _loadUrlData(url) {
var self = this;
return new Promise(function(resolve, reject) {
var xhr = new XMLHttpRequest;
xhr.open('GET', url, true);
xhr.onreadystatechange = function () {
if(xhr.readyState === XMLHttpRequest.DONE) {
if (xhr.status === 200) {
var data = JSON.parse(xhr.responseText);
self.cache[url] = data;
return resolve(data);
} else {
return reject([xhr.responseText, xhr.status]);
}
}
};
xhr.send();
});
},
_loadData: function _loadData(data, config, self) { _loadData: function _loadData(data, config, self) {
if (config.loadingTemplate) { if (config.loadingTemplate) {
var dataLoadingTemplate = self.hook.list.list.querySelector('[data-loading-template]'); var dataLoadingTemplate = self.hook.list.list.querySelector('[data-loading-template]');
...@@ -31,7 +14,6 @@ const Ajax = { ...@@ -31,7 +14,6 @@ const Ajax = {
init: function init(hook) { init: function init(hook) {
var self = this; var self = this;
self.destroyed = false; self.destroyed = false;
self.cache = self.cache || {};
var config = hook.config.Ajax; var config = hook.config.Ajax;
this.hook = hook; this.hook = hook;
if (!config || !config.endpoint || !config.method) { if (!config || !config.endpoint || !config.method) {
...@@ -48,14 +30,10 @@ const Ajax = { ...@@ -48,14 +30,10 @@ const Ajax = {
this.listTemplate = dynamicList.outerHTML; this.listTemplate = dynamicList.outerHTML;
dynamicList.outerHTML = loadingTemplate.outerHTML; dynamicList.outerHTML = loadingTemplate.outerHTML;
} }
if (self.cache[config.endpoint]) {
self._loadData(self.cache[config.endpoint], config, self); AjaxCache.retrieve(config.endpoint)
} else { .then((data) => self._loadData(data, config, self))
this._loadUrlData(config.endpoint) .catch(config.onError);
.then(function(d) {
self._loadData(d, config, self);
}, config.onError).catch(config.onError);
}
}, },
destroy: function() { destroy: function() {
this.destroyed = true; this.destroyed = true;
......
/* eslint-disable */ /* eslint-disable */
import AjaxCache from '../../lib/utils/ajax_cache';
const AjaxFilter = { const AjaxFilter = {
init: function(hook) { init: function(hook) {
...@@ -58,50 +59,24 @@ const AjaxFilter = { ...@@ -58,50 +59,24 @@ const AjaxFilter = {
this.loading = true; this.loading = true;
var params = config.params || {}; var params = config.params || {};
params[config.searchKey] = searchValue; params[config.searchKey] = searchValue;
var self = this;
self.cache = self.cache || {};
var url = config.endpoint + this.buildParams(params); var url = config.endpoint + this.buildParams(params);
var urlCachedData = self.cache[url]; return AjaxCache.retrieve(url)
if (urlCachedData) { .then((data) => {
self._loadData(urlCachedData, config, self); this._loadData(data, config);
} else { })
this._loadUrlData(url) .catch(config.onError);
.then(function(data) {
self._loadData(data, config, self);
}, config.onError).catch(config.onError);
}
}, },
_loadUrlData: function _loadUrlData(url) { _loadData(data, config) {
var self = this; const list = this.hook.list;
return new Promise(function(resolve, reject) {
var xhr = new XMLHttpRequest;
xhr.open('GET', url, true);
xhr.onreadystatechange = function () {
if(xhr.readyState === XMLHttpRequest.DONE) {
if (xhr.status === 200) {
var data = JSON.parse(xhr.responseText);
self.cache[url] = data;
return resolve(data);
} else {
return reject([xhr.responseText, xhr.status]);
}
}
};
xhr.send();
});
},
_loadData: function _loadData(data, config, self) {
const list = self.hook.list;
if (config.loadingTemplate && list.data === undefined || if (config.loadingTemplate && list.data === undefined ||
list.data.length === 0) { list.data.length === 0) {
const dataLoadingTemplate = list.list.querySelector('[data-loading-template]'); const dataLoadingTemplate = list.list.querySelector('[data-loading-template]');
if (dataLoadingTemplate) { if (dataLoadingTemplate) {
dataLoadingTemplate.outerHTML = self.listTemplate; dataLoadingTemplate.outerHTML = this.listTemplate;
} }
} }
if (!self.destroyed) { if (!this.destroyed) {
var hookListChildren = list.list.children; var hookListChildren = list.list.children;
var onlyDynamicList = hookListChildren.length === 1 && hookListChildren[0].hasAttribute('data-dynamic'); var onlyDynamicList = hookListChildren.length === 1 && hookListChildren[0].hasAttribute('data-dynamic');
if (onlyDynamicList && data.length === 0) { if (onlyDynamicList && data.length === 0) {
...@@ -109,7 +84,7 @@ const AjaxFilter = { ...@@ -109,7 +84,7 @@ const AjaxFilter = {
} }
list.setData.call(list, data); list.setData.call(list, data);
} }
self.notLoading(); this.notLoading();
list.currentIndex = 0; list.currentIndex = 0;
}, },
......
This diff is collapsed.
<script> <script>
import Timeago from 'timeago.js'; import Timeago from 'timeago.js';
import _ from 'underscore'; import _ from 'underscore';
import userAvatarLink from '../../vue_shared/components/user_avatar/user_avatar_link.vue';
import '../../lib/utils/text_utility'; import '../../lib/utils/text_utility';
import ActionsComponent from './environment_actions.vue'; import ActionsComponent from './environment_actions.vue';
import ExternalUrlComponent from './environment_external_url.vue'; import ExternalUrlComponent from './environment_external_url.vue';
...@@ -20,6 +21,7 @@ const timeagoInstance = new Timeago(); ...@@ -20,6 +21,7 @@ const timeagoInstance = new Timeago();
export default { export default {
components: { components: {
userAvatarLink,
'commit-component': CommitComponent, 'commit-component': CommitComponent,
'actions-component': ActionsComponent, 'actions-component': ActionsComponent,
'external-url-component': ExternalUrlComponent, 'external-url-component': ExternalUrlComponent,
...@@ -468,15 +470,13 @@ export default { ...@@ -468,15 +470,13 @@ export default {
<span v-if="!model.isFolder && deploymentHasUser"> <span v-if="!model.isFolder && deploymentHasUser">
by by
<a <user-avatar-link
:href="deploymentUser.web_url" class="js-deploy-user-container"
class="js-deploy-user-container"> :link-href="deploymentUser.web_url"
<img :img-src="deploymentUser.avatar_url"
class="avatar has-tooltip s20" :img-alt="userImageAltDescription"
:src="deploymentUser.avatar_url" :tooltip-text="deploymentUser.username"
:alt="userImageAltDescription" />
:title="deploymentUser.username" />
</a>
</span> </span>
</td> </td>
......
...@@ -21,7 +21,6 @@ export default { ...@@ -21,7 +21,6 @@ export default {
<a <a
class="btn monitoring-url has-tooltip" class="btn monitoring-url has-tooltip"
data-container="body" data-container="body"
target="_blank"
rel="noopener noreferrer nofollow" rel="noopener noreferrer nofollow"
:href="monitoringUrl" :href="monitoringUrl"
:title="title" :title="title"
......
...@@ -13,13 +13,17 @@ export default { ...@@ -13,13 +13,17 @@ export default {
required: false, required: false,
default: true, default: true,
}, },
allowedKeys: {
type: Array,
required: true,
},
}, },
computed: { computed: {
processedItems() { processedItems() {
return this.items.map((item) => { return this.items.map((item) => {
const { tokens, searchToken } const { tokens, searchToken }
= gl.FilteredSearchTokenizer.processTokens(item); = gl.FilteredSearchTokenizer.processTokens(item, this.allowedKeys);
const resultantTokens = tokens.map(token => ({ const resultantTokens = tokens.map(token => ({
prefix: `${token.key}:`, prefix: `${token.key}:`,
......
import Filter from '~/droplab/plugins/filter'; import Filter from '~/droplab/plugins/filter';
import './filtered_search_dropdown';
require('./filtered_search_dropdown');
class DropdownHint extends gl.FilteredSearchDropdown { class DropdownHint extends gl.FilteredSearchDropdown {
constructor(droplab, dropdown, input, filter) { constructor(droplab, dropdown, input, tokenKeys, filter) {
super(droplab, dropdown, input, filter); super(droplab, dropdown, input, filter);
this.config = { this.config = {
Filter: { Filter: {
template: 'hint', template: 'hint',
filterFunction: gl.DropdownUtils.filterHint.bind(null, input), filterFunction: gl.DropdownUtils.filterHint.bind(null, {
input,
allowedKeys: tokenKeys.getKeys(),
}),
}, },
}; };
this.tokenKeys = tokenKeys;
} }
itemClicked(e) { itemClicked(e) {
...@@ -53,20 +56,13 @@ class DropdownHint extends gl.FilteredSearchDropdown { ...@@ -53,20 +56,13 @@ class DropdownHint extends gl.FilteredSearchDropdown {
} }
renderContent() { renderContent() {
const dropdownData = []; const dropdownData = gl.FilteredSearchTokenKeys.get()
.map(tokenKey => ({
[].forEach.call(this.input.closest('.filtered-search-box-input-container').querySelectorAll('.dropdown-menu'), (dropdownMenu) => { icon: `fa-${tokenKey.icon}`,
const { icon, hint, tag, type } = dropdownMenu.dataset; hint: tokenKey.key,
if (icon && hint && tag) { tag: `<${tokenKey.symbol}${tokenKey.key}>`,
dropdownData.push( type: tokenKey.type,
Object.assign({ }));
icon: `fa-${icon}`,
hint,
tag: `<${tag}>`,
}, type && { type }),
);
}
});
this.droplab.changeHookList(this.hookId, this.dropdown, [Filter], this.config); this.droplab.changeHookList(this.hookId, this.dropdown, [Filter], this.config);
this.droplab.setData(this.hookId, dropdownData); this.droplab.setData(this.hookId, dropdownData);
......
...@@ -2,11 +2,10 @@ ...@@ -2,11 +2,10 @@
import Ajax from '~/droplab/plugins/ajax'; import Ajax from '~/droplab/plugins/ajax';
import Filter from '~/droplab/plugins/filter'; import Filter from '~/droplab/plugins/filter';
import './filtered_search_dropdown';
require('./filtered_search_dropdown');
class DropdownNonUser extends gl.FilteredSearchDropdown { class DropdownNonUser extends gl.FilteredSearchDropdown {
constructor(droplab, dropdown, input, filter, endpoint, symbol) { constructor(droplab, dropdown, input, tokenKeys, filter, endpoint, symbol) {
super(droplab, dropdown, input, filter); super(droplab, dropdown, input, filter);
this.symbol = symbol; this.symbol = symbol;
this.config = { this.config = {
......
/* global Flash */ /* global Flash */
import AjaxFilter from '~/droplab/plugins/ajax_filter'; import AjaxFilter from '~/droplab/plugins/ajax_filter';
import './filtered_search_dropdown';
require('./filtered_search_dropdown');
class DropdownUser extends gl.FilteredSearchDropdown { class DropdownUser extends gl.FilteredSearchDropdown {
constructor(droplab, dropdown, input, filter) { constructor(droplab, dropdown, input, tokenKeys, filter) {
super(droplab, dropdown, input, filter); super(droplab, dropdown, input, filter);
this.config = { this.config = {
AjaxFilter: { AjaxFilter: {
...@@ -26,6 +25,7 @@ class DropdownUser extends gl.FilteredSearchDropdown { ...@@ -26,6 +25,7 @@ class DropdownUser extends gl.FilteredSearchDropdown {
}, },
}, },
}; };
this.tokenKeys = tokenKeys;
} }
itemClicked(e) { itemClicked(e) {
...@@ -44,7 +44,7 @@ class DropdownUser extends gl.FilteredSearchDropdown { ...@@ -44,7 +44,7 @@ class DropdownUser extends gl.FilteredSearchDropdown {
getSearchInput() { getSearchInput() {
const query = gl.DropdownUtils.getSearchInput(this.input); const query = gl.DropdownUtils.getSearchInput(this.input);
const { lastToken } = gl.FilteredSearchTokenizer.processTokens(query); const { lastToken } = gl.FilteredSearchTokenizer.processTokens(query, this.tokenKeys.get());
let value = lastToken || ''; let value = lastToken || '';
......
...@@ -50,10 +50,12 @@ class DropdownUtils { ...@@ -50,10 +50,12 @@ class DropdownUtils {
return updatedItem; return updatedItem;
} }
static filterHint(input, item) { static filterHint(config, item) {
const { input, allowedKeys } = config;
const updatedItem = item; const updatedItem = item;
const searchInput = gl.DropdownUtils.getSearchQuery(input); const searchInput = gl.DropdownUtils.getSearchQuery(input);
const { lastToken, tokens } = gl.FilteredSearchTokenizer.processTokens(searchInput); const { lastToken, tokens } =
gl.FilteredSearchTokenizer.processTokens(searchInput, allowedKeys);
const lastKey = lastToken.key || lastToken || ''; const lastKey = lastToken.key || lastToken || '';
const allowMultiple = item.type === 'array'; const allowMultiple = item.type === 'array';
const itemInExistingTokens = tokens.some(t => t.key === item.hint); const itemInExistingTokens = tokens.some(t => t.key === item.hint);
......
require('./dropdown_hint'); import './dropdown_hint';
require('./dropdown_non_user'); import './dropdown_non_user';
require('./dropdown_user'); import './dropdown_user';
require('./dropdown_utils'); import './dropdown_utils';
require('./filtered_search_dropdown_manager'); import './filtered_search_dropdown_manager';
require('./filtered_search_dropdown'); import './filtered_search_dropdown';
require('./filtered_search_manager'); import './filtered_search_manager';
require('./filtered_search_token_keys'); import './filtered_search_token_keys';
require('./filtered_search_tokenizer'); import './filtered_search_tokenizer';
require('./filtered_search_visual_tokens'); import './filtered_search_visual_tokens';
...@@ -2,10 +2,10 @@ import DropLab from '~/droplab/drop_lab'; ...@@ -2,10 +2,10 @@ import DropLab from '~/droplab/drop_lab';
import FilteredSearchContainer from './container'; import FilteredSearchContainer from './container';
class FilteredSearchDropdownManager { class FilteredSearchDropdownManager {
constructor(baseEndpoint = '', page) { constructor(baseEndpoint = '', tokenizer, page) {
this.container = FilteredSearchContainer.container; this.container = FilteredSearchContainer.container;
this.baseEndpoint = baseEndpoint.replace(/\/$/, ''); this.baseEndpoint = baseEndpoint.replace(/\/$/, '');
this.tokenizer = gl.FilteredSearchTokenizer; this.tokenizer = tokenizer;
this.filteredSearchTokenKeys = gl.FilteredSearchTokenKeys; this.filteredSearchTokenKeys = gl.FilteredSearchTokenKeys;
this.filteredSearchInput = this.container.querySelector('.filtered-search'); this.filteredSearchInput = this.container.querySelector('.filtered-search');
this.page = page; this.page = page;
...@@ -98,7 +98,8 @@ class FilteredSearchDropdownManager { ...@@ -98,7 +98,8 @@ class FilteredSearchDropdownManager {
if (!mappingKey.reference) { if (!mappingKey.reference) {
const dl = this.droplab; const dl = this.droplab;
const defaultArguments = [null, dl, element, this.filteredSearchInput, key]; const defaultArguments =
[null, dl, element, this.filteredSearchInput, this.filteredSearchTokenKeys, key];
const glArguments = defaultArguments.concat(mappingKey.extraArguments || []); const glArguments = defaultArguments.concat(mappingKey.extraArguments || []);
// Passing glArguments to `new gl[glClass](<arguments>)` // Passing glArguments to `new gl[glClass](<arguments>)`
...@@ -141,7 +142,8 @@ class FilteredSearchDropdownManager { ...@@ -141,7 +142,8 @@ class FilteredSearchDropdownManager {
setDropdown() { setDropdown() {
const query = gl.DropdownUtils.getSearchQuery(true); const query = gl.DropdownUtils.getSearchQuery(true);
const { lastToken, searchToken } = this.tokenizer.processTokens(query); const { lastToken, searchToken } =
this.tokenizer.processTokens(query, this.filteredSearchTokenKeys.getKeys());
if (this.currentDropdown) { if (this.currentDropdown) {
this.updateCurrentDropdownOffset(); this.updateCurrentDropdownOffset();
......
...@@ -15,6 +15,7 @@ class FilteredSearchManager { ...@@ -15,6 +15,7 @@ class FilteredSearchManager {
this.recentSearchesStore = new RecentSearchesStore({ this.recentSearchesStore = new RecentSearchesStore({
isLocalStorageAvailable: RecentSearchesService.isAvailable(), isLocalStorageAvailable: RecentSearchesService.isAvailable(),
allowedKeys: this.filteredSearchTokenKeys.getKeys(),
}); });
const searchHistoryDropdownElement = document.querySelector('.js-filtered-search-history-dropdown'); const searchHistoryDropdownElement = document.querySelector('.js-filtered-search-history-dropdown');
const projectPath = searchHistoryDropdownElement ? const projectPath = searchHistoryDropdownElement ?
...@@ -46,7 +47,7 @@ class FilteredSearchManager { ...@@ -46,7 +47,7 @@ class FilteredSearchManager {
if (this.filteredSearchInput) { if (this.filteredSearchInput) {
this.tokenizer = gl.FilteredSearchTokenizer; this.tokenizer = gl.FilteredSearchTokenizer;
this.dropdownManager = new gl.FilteredSearchDropdownManager(this.filteredSearchInput.getAttribute('data-base-endpoint') || '', page); this.dropdownManager = new gl.FilteredSearchDropdownManager(this.filteredSearchInput.getAttribute('data-base-endpoint') || '', this.tokenizer, page);
this.recentSearchesRoot = new RecentSearchesRoot( this.recentSearchesRoot = new RecentSearchesRoot(
this.recentSearchesStore, this.recentSearchesStore,
...@@ -318,7 +319,7 @@ class FilteredSearchManager { ...@@ -318,7 +319,7 @@ class FilteredSearchManager {
handleInputVisualToken() { handleInputVisualToken() {
const input = this.filteredSearchInput; const input = this.filteredSearchInput;
const { tokens, searchToken } const { tokens, searchToken }
= gl.FilteredSearchTokenizer.processTokens(input.value); = this.tokenizer.processTokens(input.value, this.filteredSearchTokenKeys.getKeys());
const { isLastVisualTokenValid } const { isLastVisualTokenValid }
= gl.FilteredSearchVisualTokens.getLastVisualTokenBeforeInput(); = gl.FilteredSearchVisualTokens.getLastVisualTokenBeforeInput();
...@@ -444,7 +445,7 @@ class FilteredSearchManager { ...@@ -444,7 +445,7 @@ class FilteredSearchManager {
this.saveCurrentSearchQuery(); this.saveCurrentSearchQuery();
const { tokens, searchToken } const { tokens, searchToken }
= this.tokenizer.processTokens(searchQuery); = this.tokenizer.processTokens(searchQuery, this.filteredSearchTokenKeys.getKeys());
const currentState = gl.utils.getParameterByName('state') || 'opened'; const currentState = gl.utils.getParameterByName('state') || 'opened';
paths.push(`state=${currentState}`); paths.push(`state=${currentState}`);
......
...@@ -3,21 +3,25 @@ const tokenKeys = [{ ...@@ -3,21 +3,25 @@ const tokenKeys = [{
type: 'string', type: 'string',
param: 'username', param: 'username',
symbol: '@', symbol: '@',
icon: 'pencil',
}, { }, {
key: 'assignee', key: 'assignee',
type: 'string', type: 'string',
param: 'username', param: 'username',
symbol: '@', symbol: '@',
icon: 'user',
}, { }, {
key: 'milestone', key: 'milestone',
type: 'string', type: 'string',
param: 'title', param: 'title',
symbol: '%', symbol: '%',
icon: 'clock-o',
}, { }, {
key: 'label', key: 'label',
type: 'array', type: 'array',
param: 'name[]', param: 'name[]',
symbol: '~', symbol: '~',
icon: 'tag',
}]; }];
const alternativeTokenKeys = [{ const alternativeTokenKeys = [{
...@@ -56,6 +60,10 @@ class FilteredSearchTokenKeys { ...@@ -56,6 +60,10 @@ class FilteredSearchTokenKeys {
return tokenKeys; return tokenKeys;
} }
static getKeys() {
return tokenKeys.map(i => i.key);
}
static getAlternatives() { static getAlternatives() {
return alternativeTokenKeys; return alternativeTokenKeys;
} }
......
require('./filtered_search_token_keys'); import './filtered_search_token_keys';
class FilteredSearchTokenizer { class FilteredSearchTokenizer {
static processTokens(input) { static processTokens(input, allowedKeys) {
const allowedKeys = gl.FilteredSearchTokenKeys.get().map(i => i.key);
// Regex extracts `(token):(symbol)(value)` // Regex extracts `(token):(symbol)(value)`
// Values that start with a double quote must end in a double quote (same for single) // Values that start with a double quote must end in a double quote (same for single)
const tokenRegex = new RegExp(`(${allowedKeys.join('|')}):([~%@]?)(?:('[^']*'{0,1})|("[^"]*"{0,1})|(\\S+))`, 'g'); const tokenRegex = new RegExp(`(${allowedKeys.join('|')}):([~%@]?)(?:('[^']*'{0,1})|("[^"]*"{0,1})|(\\S+))`, 'g');
......
...@@ -37,6 +37,7 @@ class RecentSearchesRoot { ...@@ -37,6 +37,7 @@ class RecentSearchesRoot {
<recent-searches-dropdown-content <recent-searches-dropdown-content
:items="recentSearches" :items="recentSearches"
:is-local-storage-available="isLocalStorageAvailable" :is-local-storage-available="isLocalStorageAvailable"
:allowed-keys="allowedKeys"
/> />
`, `,
components: { components: {
......
import _ from 'underscore'; import _ from 'underscore';
class RecentSearchesStore { class RecentSearchesStore {
constructor(initialState = {}) { constructor(initialState = {}, allowedKeys) {
this.state = Object.assign({ this.state = Object.assign({
isLocalStorageAvailable: true, isLocalStorageAvailable: true,
recentSearches: [], recentSearches: [],
allowedKeys,
}, initialState); }, initialState);
} }
......
This diff is collapsed.
/* eslint-disable comma-dangle, class-methods-use-this, max-len, space-before-function-paren, arrow-parens, no-param-reassign */ /* eslint-disable comma-dangle, class-methods-use-this, max-len, space-before-function-paren, arrow-parens, no-param-reassign */
require('./gl_field_error'); import './gl_field_error';
const customValidationFlag = 'gl-field-error-ignore'; const customValidationFlag = 'gl-field-error-ignore';
......
...@@ -3,11 +3,14 @@ ...@@ -3,11 +3,14 @@
/* global DropzoneInput */ /* global DropzoneInput */
/* global autosize */ /* global autosize */
import GfmAutoComplete from './gfm_auto_complete';
window.gl = window.gl || {}; window.gl = window.gl || {};
function GLForm(form) { function GLForm(form, enableGFM = false) {
this.form = form; this.form = form;
this.textarea = this.form.find('textarea.js-gfm-input'); this.textarea = this.form.find('textarea.js-gfm-input');
this.enableGFM = enableGFM;
// Before we start, we should clean up any previous data for this form // Before we start, we should clean up any previous data for this form
this.destroy(); this.destroy();
// Setup the form // Setup the form
...@@ -30,8 +33,14 @@ GLForm.prototype.setupForm = function() { ...@@ -30,8 +33,14 @@ GLForm.prototype.setupForm = function() {
this.form.addClass('gfm-form'); this.form.addClass('gfm-form');
// remove notify commit author checkbox for non-commit notes // remove notify commit author checkbox for non-commit notes
gl.utils.disableButtonIfEmptyField(this.form.find('.js-note-text'), this.form.find('.js-comment-button, .js-note-new-discussion')); gl.utils.disableButtonIfEmptyField(this.form.find('.js-note-text'), this.form.find('.js-comment-button, .js-note-new-discussion'));
new GfmAutoComplete(gl.GfmAutoComplete && gl.GfmAutoComplete.dataSources).setup(this.form.find('.js-gfm-input'), {
gl.GfmAutoComplete.setup(this.form.find('.js-gfm-input')); emojis: true,
members: this.enableGFM,
issues: this.enableGFM,
milestones: this.enableGFM,
mergeRequests: this.enableGFM,
labels: this.enableGFM,
});
new DropzoneInput(this.form); new DropzoneInput(this.form);
autosize(this.textarea); autosize(this.textarea);
} }
......
...@@ -44,18 +44,18 @@ export default class GroupName { ...@@ -44,18 +44,18 @@ export default class GroupName {
showToggle() { showToggle() {
this.title.classList.add('wrap'); this.title.classList.add('wrap');
this.toggle.classList.remove('hidden'); this.toggle.classList.remove('hidden');
if (this.isHidden) this.groupTitle.classList.add('is-hidden'); if (this.isHidden) this.groupTitle.classList.add('hidden');
} }
hideToggle() { hideToggle() {
this.title.classList.remove('wrap'); this.title.classList.remove('wrap');
this.toggle.classList.add('hidden'); this.toggle.classList.add('hidden');
if (this.isHidden) this.groupTitle.classList.remove('is-hidden'); if (this.isHidden) this.groupTitle.classList.remove('hidden');
} }
toggleGroups() { toggleGroups() {
this.isHidden = !this.isHidden; this.isHidden = !this.isHidden;
this.groupTitle.classList.toggle('is-hidden'); this.groupTitle.classList.toggle('hidden');
} }
render() { render() {
......
...@@ -3,7 +3,7 @@ ...@@ -3,7 +3,7 @@
prefer-arrow-callback, comma-dangle, consistent-return, yoda, prefer-arrow-callback, comma-dangle, consistent-return, yoda,
prefer-rest-params, prefer-spread, no-unused-vars, prefer-template, prefer-rest-params, prefer-spread, no-unused-vars, prefer-template,
promise/catch-or-return */ promise/catch-or-return */
/* global Api */ import Api from './api';
var slice = [].slice; var slice = [].slice;
......
...@@ -47,7 +47,6 @@ import UsersSelect from './users_select'; ...@@ -47,7 +47,6 @@ import UsersSelect from './users_select';
Cookies.set('collapsed_gutter', true); Cookies.set('collapsed_gutter', true);
} }
}); });
$(".right-sidebar").niceScroll();
} }
IssuableContext.prototype.initParticipants = function() { IssuableContext.prototype.initParticipants = function() {
......
...@@ -6,6 +6,7 @@ ...@@ -6,6 +6,7 @@
/* global Pikaday */ /* global Pikaday */
import UsersSelect from './users_select'; import UsersSelect from './users_select';
import GfmAutoComplete from './gfm_auto_complete';
(function() { (function() {
this.IssuableForm = (function() { this.IssuableForm = (function() {
...@@ -20,7 +21,7 @@ import UsersSelect from './users_select'; ...@@ -20,7 +21,7 @@ import UsersSelect from './users_select';
this.renderWipExplanation = this.renderWipExplanation.bind(this); this.renderWipExplanation = this.renderWipExplanation.bind(this);
this.resetAutosave = this.resetAutosave.bind(this); this.resetAutosave = this.resetAutosave.bind(this);
this.handleSubmit = this.handleSubmit.bind(this); this.handleSubmit = this.handleSubmit.bind(this);
gl.GfmAutoComplete.setup(); new GfmAutoComplete(gl.GfmAutoComplete && gl.GfmAutoComplete.dataSources).setup();
new UsersSelect(); new UsersSelect();
new ZenMode(); new ZenMode();
this.titleField = this.form.find("input[name*='[title]']"); this.titleField = this.form.find("input[name*='[title]']");
......
/* eslint-disable func-names, space-before-function-paren, no-var, prefer-rest-params, wrap-iife, one-var, no-underscore-dangle, one-var-declaration-per-line, object-shorthand, no-unused-vars, no-new, comma-dangle, consistent-return, quotes, dot-notation, quote-props, prefer-arrow-callback, max-len */ /* eslint-disable func-names, space-before-function-paren, no-var, prefer-rest-params, wrap-iife, one-var, no-underscore-dangle, one-var-declaration-per-line, object-shorthand, no-unused-vars, no-new, comma-dangle, consistent-return, quotes, dot-notation, quote-props, prefer-arrow-callback, max-len */
/* global Flash */ /* global Flash */
import CreateMergeRequestDropdown from './create_merge_request_dropdown';
require('./flash'); import 'vendor/jquery.waitforimages';
require('~/lib/utils/text_utility'); import '~/lib/utils/text_utility';
require('vendor/jquery.waitforimages'); import './flash';
require('./task_list'); import './task_list';
import CreateMergeRequestDropdown from './create_merge_request_dropdown';
class Issue { class Issue {
constructor() { constructor() {
......
class AjaxCache { import Cache from './cache';
class AjaxCache extends Cache {
constructor() { constructor() {
this.internalStorage = { }; super();
this.pendingRequests = { }; this.pendingRequests = { };
} }
get(endpoint) {
return this.internalStorage[endpoint];
}
hasData(endpoint) {
return Object.prototype.hasOwnProperty.call(this.internalStorage, endpoint);
}
remove(endpoint) {
delete this.internalStorage[endpoint];
}
retrieve(endpoint) { retrieve(endpoint) {
if (this.hasData(endpoint)) { if (this.hasData(endpoint)) {
return Promise.resolve(this.get(endpoint)); return Promise.resolve(this.get(endpoint));
......
class Cache {
constructor() {
this.internalStorage = { };
}
get(key) {
return this.internalStorage[key];
}
hasData(key) {
return Object.prototype.hasOwnProperty.call(this.internalStorage, key);
}
remove(key) {
delete this.internalStorage[key];
}
}
export default Cache;
...@@ -135,7 +135,10 @@ ...@@ -135,7 +135,10 @@
gl.utils.getUrlParamsArray = function () { gl.utils.getUrlParamsArray = function () {
// We can trust that each param has one & since values containing & will be encoded // We can trust that each param has one & since values containing & will be encoded
// Remove the first character of search as it is always ? // Remove the first character of search as it is always ?
return window.location.search.slice(1).split('&'); return window.location.search.slice(1).split('&').map((param) => {
const split = param.split('=');
return [decodeURI(split[0]), split[1]].join('=');
});
}; };
gl.utils.isMetaKey = function(e) { gl.utils.isMetaKey = function(e) {
...@@ -195,10 +198,12 @@ ...@@ -195,10 +198,12 @@
const textBefore = value.substring(0, selectionStart); const textBefore = value.substring(0, selectionStart);
const textAfter = value.substring(selectionEnd, value.length); const textAfter = value.substring(selectionEnd, value.length);
const newText = textBefore + text + textAfter;
const insertedText = text instanceof Function ? text(textBefore, textAfter) : text;
const newText = textBefore + insertedText + textAfter;
target.value = newText; target.value = newText;
target.selectionStart = target.selectionEnd = selectionStart + text.length; target.selectionStart = target.selectionEnd = selectionStart + insertedText.length;
// Trigger autosave // Trigger autosave
$(target).trigger('input'); $(target).trigger('input');
......
/* eslint-disable func-names, space-before-function-paren, wrap-iife, no-var, no-param-reassign, no-cond-assign, comma-dangle, no-unused-expressions, prefer-template, max-len */ /* eslint-disable func-names, space-before-function-paren, wrap-iife, no-var, no-param-reassign, no-cond-assign, comma-dangle, no-unused-expressions, prefer-template, max-len */
/* global timeago */
/* global dateFormat */
window.timeago = require('timeago.js'); import timeago from 'timeago.js';
window.dateFormat = require('vendor/date.format'); import dateFormat from 'vendor/date.format';
window.timeago = timeago;
window.dateFormat = dateFormat;
(function() { (function() {
(function(w) { (function(w) {
...@@ -101,8 +102,7 @@ window.dateFormat = require('vendor/date.format'); ...@@ -101,8 +102,7 @@ window.dateFormat = require('vendor/date.format');
}; };
w.gl.utils.updateTimeagoText = function(el) { w.gl.utils.updateTimeagoText = function(el) {
const timeago = gl.utils.getTimeago(); const formattedDate = gl.utils.getTimeago().format(el.getAttribute('datetime'), 'gl_en');
const formattedDate = timeago.format(el.getAttribute('datetime'), 'gl_en');
if (el.textContent !== formattedDate) { if (el.textContent !== formattedDate) {
el.textContent = formattedDate; el.textContent = formattedDate;
......
...@@ -2,9 +2,7 @@ ...@@ -2,9 +2,7 @@
* exports HTTP status codes * exports HTTP status codes
*/ */
const statusCodes = { export default {
NO_CONTENT: 204, NO_CONTENT: 204,
OK: 200, OK: 200,
}; };
module.exports = statusCodes;
/* eslint-disable func-names, space-before-function-paren, wrap-iife, no-var, no-param-reassign, no-cond-assign, quotes, one-var, one-var-declaration-per-line, operator-assignment, no-else-return, prefer-template, prefer-arrow-callback, no-empty, max-len, consistent-return, no-unused-vars, no-return-assign, max-len, vars-on-top */ /* eslint-disable func-names, space-before-function-paren, wrap-iife, no-var, no-param-reassign, no-cond-assign, quotes, one-var, one-var-declaration-per-line, operator-assignment, no-else-return, prefer-template, prefer-arrow-callback, no-empty, max-len, consistent-return, no-unused-vars, no-return-assign, max-len, vars-on-top */
require('vendor/latinise');
import 'vendor/latinise';
var base; var base;
var w = window; var w = window;
......
import Api from '../../api';
import Cache from './cache';
class UsersCache extends Cache {
retrieve(username) {
if (this.hasData(username)) {
return Promise.resolve(this.get(username));
}
return Api.users('', { username })
.then((users) => {
if (!users.length) {
throw new Error(`User "${username}" could not be found!`);
}
if (users.length > 1) {
throw new Error(`Expected username "${username}" to be unique!`);
}
const user = users[0];
this.internalStorage[username] = user;
return user;
});
// missing catch is intentional, error handling depends on use case
}
}
export default new UsersCache();
...@@ -4,8 +4,6 @@ ...@@ -4,8 +4,6 @@
// //
// Handles single- and multi-line selection and highlight for blob views. // Handles single- and multi-line selection and highlight for blob views.
// //
require('vendor/jquery.scrollTo');
// //
// ### Example Markup // ### Example Markup
// //
......
This diff is collapsed.
...@@ -96,7 +96,6 @@ import './dropzone_input'; ...@@ -96,7 +96,6 @@ import './dropzone_input';
import './due_date_select'; import './due_date_select';
import './files_comment_button'; import './files_comment_button';
import './flash'; import './flash';
import './gfm_auto_complete';
import './gl_dropdown'; import './gl_dropdown';
import './gl_field_error'; import './gl_field_error';
import './gl_field_errors'; import './gl_field_errors';
...@@ -171,7 +170,7 @@ import './visibility_select'; ...@@ -171,7 +170,7 @@ import './visibility_select';
import './wikis'; import './wikis';
import './zen_mode'; import './zen_mode';
// eslint-disable-next-line global-require // eslint-disable-next-line global-require, import/no-commonjs
if (process.env.NODE_ENV !== 'production') require('./test_utils/'); if (process.env.NODE_ENV !== 'production') require('./test_utils/');
document.addEventListener('beforeunload', function () { document.addEventListener('beforeunload', function () {
......
...@@ -2,14 +2,13 @@ ...@@ -2,14 +2,13 @@
/* global Flash */ /* global Flash */
import Vue from 'vue'; import Vue from 'vue';
import './merge_conflict_store';
require('./merge_conflict_store'); import './merge_conflict_service';
require('./merge_conflict_service'); import './mixins/line_conflict_utils';
require('./mixins/line_conflict_utils'); import './mixins/line_conflict_actions';
require('./mixins/line_conflict_actions'); import './components/diff_file_editor';
require('./components/diff_file_editor'); import './components/inline_conflict_lines';
require('./components/inline_conflict_lines'); import './components/parallel_conflict_lines';
require('./components/parallel_conflict_lines');
$(() => { $(() => {
const INTERACTIVE_RESOLVE_MODE = 'interactive'; const INTERACTIVE_RESOLVE_MODE = 'interactive';
......
/* eslint-disable func-names, space-before-function-paren, no-var, prefer-rest-params, wrap-iife, quotes, no-underscore-dangle, one-var, one-var-declaration-per-line, consistent-return, dot-notation, quote-props, comma-dangle, object-shorthand, max-len, prefer-arrow-callback */ /* eslint-disable func-names, space-before-function-paren, no-var, prefer-rest-params, wrap-iife, quotes, no-underscore-dangle, one-var, one-var-declaration-per-line, consistent-return, dot-notation, quote-props, comma-dangle, object-shorthand, max-len, prefer-arrow-callback */
/* global MergeRequestTabs */ /* global MergeRequestTabs */
require('vendor/jquery.waitforimages'); import 'vendor/jquery.waitforimages';
require('./task_list'); import './task_list';
require('./merge_request_tabs'); import './merge_request_tabs';
(function() { (function() {
this.MergeRequest = (function() { this.MergeRequest = (function() {
......
/* eslint-disable no-new, class-methods-use-this */ /* eslint-disable no-new, class-methods-use-this */
/* global Breakpoints */ /* global Breakpoints */
/* global Flash */ /* global Flash */
/* global notes */
import Cookies from 'js-cookie'; import Cookies from 'js-cookie';
import './breakpoints'; import './breakpoints';
...@@ -251,7 +252,8 @@ import BlobForkSuggestion from './blob/blob_fork_suggestion'; ...@@ -251,7 +252,8 @@ import BlobForkSuggestion from './blob/blob_fork_suggestion';
this.ajaxGet({ this.ajaxGet({
url: `${urlPathname}.json${location.search}`, url: `${urlPathname}.json${location.search}`,
success: (data) => { success: (data) => {
$('#diffs').html(data.html); const $container = $('#diffs');
$container.html(data.html);
if (typeof gl.diffNotesCompileComponents !== 'undefined') { if (typeof gl.diffNotesCompileComponents !== 'undefined') {
gl.diffNotesCompileComponents(); gl.diffNotesCompileComponents();
...@@ -278,6 +280,24 @@ import BlobForkSuggestion from './blob/blob_fork_suggestion'; ...@@ -278,6 +280,24 @@ import BlobForkSuggestion from './blob/blob_fork_suggestion';
}) })
.init(); .init();
}); });
// Scroll any linked note into view
// Similar to `toggler_behavior` in the discussion tab
const hash = window.gl.utils.getLocationHash();
const anchor = hash && $container.find(`[id="${hash}"]`);
if (anchor) {
const notesContent = anchor.closest('.notes_content');
const lineType = notesContent.hasClass('new') ? 'new' : 'old';
notes.toggleDiffNote({
target: anchor,
lineType,
forceShow: true,
});
anchor[0].scrollIntoView();
// We have multiple elements on the page with `#note_xxx`
// (discussion and diff tabs) and `:target` only applies to the first
anchor.addClass('target');
}
}, },
}); });
} }
......
This diff is collapsed.
/* eslint-disable func-names, space-before-function-paren, no-var, prefer-rest-params, wrap-iife, one-var, vars-on-top, one-var-declaration-per-line, comma-dangle, object-shorthand, no-else-return, prefer-template, quotes, prefer-arrow-callback, no-param-reassign, no-cond-assign, max-len */ /* eslint-disable func-names, space-before-function-paren, no-var, prefer-rest-params, wrap-iife, one-var, vars-on-top, one-var-declaration-per-line, comma-dangle, object-shorthand, no-else-return, prefer-template, quotes, prefer-arrow-callback, no-param-reassign, no-cond-assign, max-len */
/* global Api */ import Api from './api';
(function() { (function() {
window.NamespaceSelect = (function() { window.NamespaceSelect = (function() {
......
/* eslint-disable func-names, space-before-function-paren, no-var, one-var, prefer-rest-params, max-len, vars-on-top, wrap-iife, consistent-return, comma-dangle, one-var-declaration-per-line, quotes, no-return-assign, prefer-arrow-callback, prefer-template, no-shadow, no-else-return, max-len, object-shorthand */ /* eslint-disable func-names, space-before-function-paren, no-var, one-var, prefer-rest-params, max-len, vars-on-top, wrap-iife, consistent-return, comma-dangle, one-var-declaration-per-line, quotes, no-return-assign, prefer-arrow-callback, prefer-template, no-shadow, no-else-return, max-len, object-shorthand */
import RefSelectDropdown from '~/ref_select_dropdown';
(function() { (function() {
this.NewBranchForm = (function() { this.NewBranchForm = (function() {
function NewBranchForm(form, availableRefs) { function NewBranchForm(form, availableRefs) {
...@@ -6,7 +8,7 @@ ...@@ -6,7 +8,7 @@
this.branchNameError = form.find('.js-branch-name-error'); this.branchNameError = form.find('.js-branch-name-error');
this.name = form.find('.js-branch-name'); this.name = form.find('.js-branch-name');
this.ref = form.find('#ref'); this.ref = form.find('#ref');
this.setupAvailableRefs(availableRefs); new RefSelectDropdown($('.js-branch-select'), availableRefs); // eslint-disable-line no-new
this.setupRestrictions(); this.setupRestrictions();
this.addBinding(); this.addBinding();
this.init(); this.init();
...@@ -22,49 +24,6 @@ ...@@ -22,49 +24,6 @@
} }
}; };
NewBranchForm.prototype.setupAvailableRefs = function(availableRefs) {
var $branchSelect = $('.js-branch-select');
$branchSelect.glDropdown({
data: availableRefs,
filterable: true,
filterByText: true,
remote: false,
fieldName: $branchSelect.data('field-name'),
filterInput: 'input[type="search"]',
selectable: true,
isSelectable: function(branch, $el) {
return !$el.hasClass('is-active');
},
text: function(branch) {
return branch;
},
id: function(branch) {
return branch;
},
toggleLabel: function(branch) {
if (branch) {
return branch;
}
}
});
const $dropdownContainer = $branchSelect.closest('.dropdown');
const $fieldInput = $(`input[name="${$branchSelect.data('field-name')}"]`, $dropdownContainer);
const $filterInput = $('input[type="search"]', $dropdownContainer);
$filterInput.on('keyup', (e) => {
const keyCode = e.keyCode || e.which;
if (keyCode !== 13) return;
const text = $filterInput.val();
$fieldInput.val(text);
$('.dropdown-toggle-text', $branchSelect).text(text);
$dropdownContainer.removeClass('open');
});
};
NewBranchForm.prototype.setupRestrictions = function() { NewBranchForm.prototype.setupRestrictions = function() {
var endsWith, invalid, single, startsWith; var endsWith, invalid, single, startsWith;
startsWith = { startsWith = {
......
This diff is collapsed.
require('~/lib/utils/common_utils'); import '~/lib/utils/common_utils';
require('~/lib/utils/url_utility'); import '~/lib/utils/url_utility';
(() => { (() => {
const ENDLESS_SCROLL_BOTTOM_PX = 400; const ENDLESS_SCROLL_BOTTOM_PX = 400;
......
...@@ -24,9 +24,6 @@ export default { ...@@ -24,9 +24,6 @@ export default {
}; };
}, },
computed: { computed: {
showUnsetWarning() {
return this.cronInterval === '';
},
intervalIsPreset() { intervalIsPreset() {
return _.contains(this.cronIntervalPresets, this.cronInterval); return _.contains(this.cronIntervalPresets, this.cronInterval);
}, },
...@@ -63,67 +60,75 @@ export default { ...@@ -63,67 +60,75 @@ export default {
}, },
template: ` template: `
<div class="interval-pattern-form-group"> <div class="interval-pattern-form-group">
<input <div class="cron-preset-radio-input">
id="custom" <input
class="label-light" id="custom"
type="radio" class="label-light"
:name="inputNameAttribute" type="radio"
:value="cronInterval" :name="inputNameAttribute"
:checked="isEditable" :value="cronInterval"
@click="toggleCustomInput(true)" :checked="isEditable"
/> @click="toggleCustomInput(true)"
/>
<label for="custom"> <label for="custom">
Custom Custom
</label> </label>
<span class="cron-syntax-link-wrap"> <span class="cron-syntax-link-wrap">
(<a :href="cronSyntaxUrl" target="_blank">Cron syntax</a>) (<a :href="cronSyntaxUrl" target="_blank">Cron syntax</a>)
</span> </span>
</div>
<input <div class="cron-preset-radio-input">
id="every-day" <input
class="label-light" id="every-day"
type="radio" class="label-light"
v-model="cronInterval" type="radio"
:name="inputNameAttribute" v-model="cronInterval"
:value="cronIntervalPresets.everyDay" :name="inputNameAttribute"
@click="toggleCustomInput(false)" :value="cronIntervalPresets.everyDay"
/> @click="toggleCustomInput(false)"
/>
<label class="label-light" for="every-day"> <label class="label-light" for="every-day">
Every day (at 4:00am) Every day (at 4:00am)
</label> </label>
</div>
<input <div class="cron-preset-radio-input">
id="every-week" <input
class="label-light" id="every-week"
type="radio" class="label-light"
v-model="cronInterval" type="radio"
:name="inputNameAttribute" v-model="cronInterval"
:value="cronIntervalPresets.everyWeek" :name="inputNameAttribute"
@click="toggleCustomInput(false)" :value="cronIntervalPresets.everyWeek"
/> @click="toggleCustomInput(false)"
/>
<label class="label-light" for="every-week"> <label class="label-light" for="every-week">
Every week (Sundays at 4:00am) Every week (Sundays at 4:00am)
</label> </label>
</div>
<input <div class="cron-preset-radio-input">
id="every-month" <input
class="label-light" id="every-month"
type="radio" class="label-light"
v-model="cronInterval" type="radio"
:name="inputNameAttribute" v-model="cronInterval"
:value="cronIntervalPresets.everyMonth" :name="inputNameAttribute"
@click="toggleCustomInput(false)" :value="cronIntervalPresets.everyMonth"
/> @click="toggleCustomInput(false)"
/>
<label class="label-light" for="every-month"> <label class="label-light" for="every-month">
Every month (on the 1st at 4:00am) Every month (on the 1st at 4:00am)
</label> </label>
</div>
<div class="cron-interval-input-wrapper col-md-6"> <div class="cron-interval-input-wrapper">
<input <input
id="schedule_cron" id="schedule_cron"
class="form-control inline cron-interval-input" class="form-control inline cron-interval-input"
...@@ -135,9 +140,6 @@ export default { ...@@ -135,9 +140,6 @@ export default {
:disabled="!isEditable" :disabled="!isEditable"
/> />
</div> </div>
<span class="cron-unset-status col-md-3" v-if="showUnsetWarning">
Schedule not yet set
</span>
</div> </div>
`, `,
}; };
...@@ -3,7 +3,7 @@ export default class TargetBranchDropdown { ...@@ -3,7 +3,7 @@ export default class TargetBranchDropdown {
this.$dropdown = $('.js-target-branch-dropdown'); this.$dropdown = $('.js-target-branch-dropdown');
this.$dropdownToggle = this.$dropdown.find('.dropdown-toggle-text'); this.$dropdownToggle = this.$dropdown.find('.dropdown-toggle-text');
this.$input = $('#schedule_ref'); this.$input = $('#schedule_ref');
this.initialValue = this.$input.val(); this.initDefaultBranch();
this.initDropdown(); this.initDropdown();
} }
...@@ -29,13 +29,23 @@ export default class TargetBranchDropdown { ...@@ -29,13 +29,23 @@ export default class TargetBranchDropdown {
} }
setDropdownToggle() { setDropdownToggle() {
if (this.initialValue) { const initialValue = this.$input.val();
this.$dropdownToggle.text(this.initialValue);
this.$dropdownToggle.text(initialValue);
}
initDefaultBranch() {
const initialValue = this.$input.val();
const defaultBranch = this.$dropdown.data('defaultBranch');
if (!initialValue) {
this.$input.val(defaultBranch);
} }
} }
updateInputValue({ selectedObj, e }) { updateInputValue({ selectedObj, e }) {
e.preventDefault(); e.preventDefault();
this.$input.val(selectedObj.name); this.$input.val(selectedObj.name);
gl.pipelineScheduleFieldErrors.updateFormValidityState(); gl.pipelineScheduleFieldErrors.updateFormValidityState();
} }
......
/* eslint-disable class-methods-use-this */ /* eslint-disable class-methods-use-this */
const defaultTimezone = 'UTC';
export default class TimezoneDropdown { export default class TimezoneDropdown {
constructor() { constructor() {
this.$dropdown = $('.js-timezone-dropdown'); this.$dropdown = $('.js-timezone-dropdown');
this.$dropdownToggle = this.$dropdown.find('.dropdown-toggle-text'); this.$dropdownToggle = this.$dropdown.find('.dropdown-toggle-text');
this.$input = $('#schedule_cron_timezone'); this.$input = $('#schedule_cron_timezone');
this.timezoneData = this.$dropdown.data('data'); this.timezoneData = this.$dropdown.data('data');
this.initialValue = this.$input.val(); this.initDefaultTimezone();
this.initDropdown(); this.initDropdown();
} }
...@@ -42,12 +44,20 @@ export default class TimezoneDropdown { ...@@ -42,12 +44,20 @@ export default class TimezoneDropdown {
return `[UTC ${this.formatUtcOffset(item.offset)}] ${item.name}`; return `[UTC ${this.formatUtcOffset(item.offset)}] ${item.name}`;
} }
setDropdownToggle() { initDefaultTimezone() {
if (this.initialValue) { const initialValue = this.$input.val();
this.$dropdownToggle.text(this.initialValue);
if (!initialValue) {
this.$input.val(defaultTimezone);
} }
} }
setDropdownToggle() {
const initialValue = this.$input.val();
this.$dropdownToggle.text(initialValue);
}
updateInputValue({ selectedObj, e }) { updateInputValue({ selectedObj, e }) {
e.preventDefault(); e.preventDefault();
this.$input.val(selectedObj.identifier); this.$input.val(selectedObj.identifier);
......
import userAvatarLink from '../../vue_shared/components/user_avatar/user_avatar_link.vue';
export default { export default {
props: [ props: [
'pipeline', 'pipeline',
...@@ -7,6 +9,9 @@ export default { ...@@ -7,6 +9,9 @@ export default {
return !!this.pipeline.user; return !!this.pipeline.user;
}, },
}, },
components: {
userAvatarLink,
},
template: ` template: `
<td> <td>
<a <a
...@@ -15,18 +20,13 @@ export default { ...@@ -15,18 +20,13 @@ export default {
<span class="pipeline-id">#{{pipeline.id}}</span> <span class="pipeline-id">#{{pipeline.id}}</span>
</a> </a>
<span>by</span> <span>by</span>
<a <user-avatar-link
class="js-pipeline-url-user"
v-if="user" v-if="user"
:href="pipeline.user.web_url"> class="js-pipeline-url-user"
<img :link-href="pipeline.user.web_url"
v-if="user" :img-src="pipeline.user.avatar_url"
class="avatar has-tooltip s20 " :tooltip-text="pipeline.user.name"
:title="pipeline.user.name" />
data-container="body"
:src="pipeline.user.avatar_url"
>
</a>
<span <span
v-if="!user" v-if="!user"
class="js-pipeline-url-api api"> class="js-pipeline-url-api api">
......
import Visibility from 'visibilityjs'; import Visibility from 'visibilityjs';
import PipelinesService from './services/pipelines_service'; import PipelinesService from './services/pipelines_service';
import eventHub from './event_hub'; import eventHub from './event_hub';
import PipelinesTableComponent from '../vue_shared/components/pipelines_table'; import pipelinesTableComponent from '../vue_shared/components/pipelines_table';
import tablePagination from '../vue_shared/components/table_pagination.vue'; import tablePagination from '../vue_shared/components/table_pagination.vue';
import EmptyState from './components/empty_state.vue'; import emptyState from './components/empty_state.vue';
import ErrorState from './components/error_state.vue'; import errorState from './components/error_state.vue';
import NavigationTabs from './components/navigation_tabs'; import navigationTabs from './components/navigation_tabs';
import NavigationControls from './components/nav_controls'; import navigationControls from './components/nav_controls';
import loadingIcon from '../vue_shared/components/loading_icon.vue'; import loadingIcon from '../vue_shared/components/loading_icon.vue';
import Poll from '../lib/utils/poll'; import Poll from '../lib/utils/poll';
...@@ -20,11 +20,11 @@ export default { ...@@ -20,11 +20,11 @@ export default {
components: { components: {
tablePagination, tablePagination,
'pipelines-table-component': PipelinesTableComponent, pipelinesTableComponent,
'empty-state': EmptyState, emptyState,
'error-state': ErrorState, errorState,
'navigation-tabs': NavigationTabs, navigationTabs,
'navigation-controls': NavigationControls, navigationControls,
loadingIcon, loadingIcon,
}, },
...@@ -52,6 +52,7 @@ export default { ...@@ -52,6 +52,7 @@ export default {
hasError: false, hasError: false,
isMakingRequest: false, isMakingRequest: false,
updateGraphDropdown: false, updateGraphDropdown: false,
hasMadeRequest: false,
}; };
}, },
...@@ -78,6 +79,7 @@ export default { ...@@ -78,6 +79,7 @@ export default {
shouldRenderEmptyState() { shouldRenderEmptyState() {
return !this.isLoading && return !this.isLoading &&
!this.hasError && !this.hasError &&
this.hasMadeRequest &&
!this.state.pipelines.length && !this.state.pipelines.length &&
(this.scope === 'all' || this.scope === null); (this.scope === 'all' || this.scope === null);
}, },
...@@ -150,6 +152,10 @@ export default { ...@@ -150,6 +152,10 @@ export default {
if (!Visibility.hidden()) { if (!Visibility.hidden()) {
this.isLoading = true; this.isLoading = true;
poll.makeRequest(); poll.makeRequest();
} else {
// If tab is not visible we need to make the first request so we don't show the empty
// state without knowing if there are any pipelines
this.fetchPipelines();
} }
Visibility.change(() => { Visibility.change(() => {
...@@ -202,6 +208,7 @@ export default { ...@@ -202,6 +208,7 @@ export default {
this.isLoading = false; this.isLoading = false;
this.updateGraphDropdown = true; this.updateGraphDropdown = true;
this.hasMadeRequest = true;
}, },
errorCallback() { errorCallback() {
......
require('./gl_crop'); import './gl_crop';
require('./profile'); import './profile';
export default function setupProjectEdit() {
const $transferForm = $('.js-project-transfer-form');
const $selectNamespace = $transferForm.find('.select2');
$selectNamespace.on('change', () => {
$transferForm.find(':submit').prop('disabled', !$selectNamespace.val());
});
$selectNamespace.trigger('change');
}
/* eslint-disable func-names, space-before-function-paren, wrap-iife, prefer-arrow-callback, no-var, comma-dangle, object-shorthand, one-var, one-var-declaration-per-line, no-else-return, quotes, max-len */ /* eslint-disable func-names, space-before-function-paren, wrap-iife, prefer-arrow-callback, no-var, comma-dangle, object-shorthand, one-var, one-var-declaration-per-line, no-else-return, quotes, max-len */
/* global Api */ import Api from './api';
(function() { (function() {
this.ProjectSelect = (function() { this.ProjectSelect = (function() {
......
require('./protected_branch_access_dropdown'); import './protected_branch_access_dropdown';
require('./protected_branch_create'); import './protected_branch_create';
require('./protected_branch_dropdown'); import './protected_branch_dropdown';
require('./protected_branch_edit'); import './protected_branch_edit';
require('./protected_branch_edit_list'); import './protected_branch_edit_list';
...@@ -6,6 +6,10 @@ const index = function index() { ...@@ -6,6 +6,10 @@ const index = function index() {
currentUserId: gon.current_user_id, currentUserId: gon.current_user_id,
whitelistUrls: [gon.gitlab_url], whitelistUrls: [gon.gitlab_url],
isProduction: process.env.NODE_ENV, isProduction: process.env.NODE_ENV,
release: gon.revision,
tags: {
revision: gon.revision,
},
}); });
return RavenConfig; return RavenConfig;
......
import Raven from 'raven-js'; import Raven from 'raven-js';
import $ from 'jquery';
const IGNORE_ERRORS = [ const IGNORE_ERRORS = [
// Random plugins/extensions // Random plugins/extensions
...@@ -57,6 +58,8 @@ const RavenConfig = { ...@@ -57,6 +58,8 @@ const RavenConfig = {
configure() { configure() {
Raven.config(this.options.sentryDsn, { Raven.config(this.options.sentryDsn, {
release: this.options.release,
tags: this.options.tags,
whitelistUrls: this.options.whitelistUrls, whitelistUrls: this.options.whitelistUrls,
environment: this.options.isProduction ? 'production' : 'development', environment: this.options.isProduction ? 'production' : 'development',
ignoreErrors: this.IGNORE_ERRORS, ignoreErrors: this.IGNORE_ERRORS,
...@@ -72,7 +75,7 @@ const RavenConfig = { ...@@ -72,7 +75,7 @@ const RavenConfig = {
}, },
bindRavenErrors() { bindRavenErrors() {
window.$(document).on('ajaxError.raven', this.handleRavenErrors); $(document).on('ajaxError.raven', this.handleRavenErrors);
}, },
handleRavenErrors(event, req, config, err) { handleRavenErrors(event, req, config, err) {
......
class RefSelectDropdown {
constructor($dropdownButton, availableRefs) {
$dropdownButton.glDropdown({
data: availableRefs,
filterable: true,
filterByText: true,
remote: false,
fieldName: $dropdownButton.data('field-name'),
filterInput: 'input[type="search"]',
selectable: true,
isSelectable(branch, $el) {
return !$el.hasClass('is-active');
},
text(branch) {
return branch;
},
id(branch) {
return branch;
},
toggleLabel(branch) {
return branch;
},
});
const $dropdownContainer = $dropdownButton.closest('.dropdown');
const $fieldInput = $(`input[name="${$dropdownButton.data('field-name')}"]`, $dropdownContainer);
const $filterInput = $('input[type="search"]', $dropdownContainer);
$filterInput.on('keyup', (e) => {
const keyCode = e.keyCode || e.which;
if (keyCode !== 13) return;
const ref = $filterInput.val().trim();
if (ref === '') {
return;
}
$fieldInput.val(ref);
$('.dropdown-toggle-text', $dropdownButton).text(ref);
$dropdownContainer.removeClass('open');
});
}
}
export default RefSelectDropdown;
/* eslint-disable func-names, space-before-function-paren, wrap-iife, no-var, one-var, one-var-declaration-per-line, object-shorthand, prefer-arrow-callback, comma-dangle, prefer-template, quotes, no-else-return, max-len */ /* eslint-disable func-names, space-before-function-paren, wrap-iife, no-var, one-var, one-var-declaration-per-line, object-shorthand, prefer-arrow-callback, comma-dangle, prefer-template, quotes, no-else-return, max-len */
/* global Flash */ /* global Flash */
/* global Api */ import Api from './api';
(function() { (function() {
this.Search = (function() { this.Search = (function() {
......
/* global Mousetrap */ /* global Mousetrap */
/* global Shortcuts */ /* global Shortcuts */
require('./shortcuts'); import './shortcuts';
const defaults = { const defaults = {
skipResetBindings: false, skipResetBindings: false,
fileBlobPermalinkUrl: null, fileBlobPermalinkUrl: null,
}; };
class ShortcutsBlob extends Shortcuts { export default class ShortcutsBlob extends Shortcuts {
constructor(opts) { constructor(opts) {
const options = Object.assign({}, defaults, opts); const options = Object.assign({}, defaults, opts);
super(options.skipResetBindings); super(options.skipResetBindings);
...@@ -25,5 +25,3 @@ class ShortcutsBlob extends Shortcuts { ...@@ -25,5 +25,3 @@ class ShortcutsBlob extends Shortcuts {
} }
} }
} }
module.exports = ShortcutsBlob;
...@@ -2,7 +2,7 @@ ...@@ -2,7 +2,7 @@
/* global Mousetrap */ /* global Mousetrap */
/* global ShortcutsNavigation */ /* global ShortcutsNavigation */
require('./shortcuts_navigation'); import './shortcuts_navigation';
(function() { (function() {
var extend = function(child, parent) { for (var key in parent) { if (hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; }, var extend = function(child, parent) { for (var key in parent) { if (hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; },
......
/* eslint-disable func-names, space-before-function-paren, max-len, no-var, one-var, no-restricted-syntax, vars-on-top, no-use-before-define, no-param-reassign, new-cap, no-underscore-dangle, wrap-iife, prefer-arrow-callback, consistent-return, no-return-assign */ /* eslint-disable func-names, space-before-function-paren, max-len, no-var, one-var, no-restricted-syntax, vars-on-top, no-use-before-define, no-param-reassign, new-cap, no-underscore-dangle, wrap-iife, prefer-arrow-callback, consistent-return, no-return-assign */
/* global Mousetrap */ /* global Mousetrap */
/* global Shortcuts */ /* global Shortcuts */
import findAndFollowLink from './shortcuts_dashboard_navigation';
require('./shortcuts'); import findAndFollowLink from './shortcuts_dashboard_navigation';
import './shortcuts';
(function() { (function() {
var extend = function(child, parent) { for (var key in parent) { if (hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; }, var extend = function(child, parent) { for (var key in parent) { if (hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; },
......
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
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