Commit b5c80f99 authored by Filipa Lacerda's avatar Filipa Lacerda

Merge branch 'master' into 27574-pipelines-empty-state

* master: (209 commits)
  Fix Slack link when on notify
  Use Enumerable#index_by where possible
  Protect against unknown emojis in frequently used list
  Fix config option to disable Prometheus
  Fix double click token name
  fix describe block to make the karma reporter happy
  removes n+1 query from tags and branches indexes
  Fix broken links limit lines to 80 chars
  Add prometheus memory requirements, include additional detail on disabling prometheus in docs.
  Add `requirements: { id: %r{[^/]+} }` for all projects and groups namespaced API routes
  Expand on the good changelog/bad changelog documentation examples
  Add policy for closing stale merge requests
  Fix spec
  Use code icon for Raw
  Fix spec
  Add copy button to blob header and use icon for Raw button
  Fix archive prefix bug for refs containing dots
  Include routes when loading user projects
  Fixed search not working in issue boards modal
  Document a New Branch feature for Bare Projects
  ...
parents 6fb66321 4bf4612c
......@@ -2,3 +2,4 @@
lib/gitlab/sanitizers/svg/whitelist.rb
lib/gitlab/diff/position_tracer.rb
app/policies/project_policy.rb
app/models/concerns/relative_positioning.rb
*.js.es6 gitlab-language=javascript
......@@ -7,8 +7,6 @@ cache:
variables:
MYSQL_ALLOW_EMPTY_PASSWORD: "1"
# retry tests only in CI environment
RSPEC_RETRY_RETRY_COUNT: "3"
RAILS_ENV: "test"
SIMPLECOV: "true"
SETUP_DB: "true"
......@@ -60,7 +58,7 @@ stages:
<<: *dedicated-runner
<<: *use-db
script:
- JOB_NAME=( $CI_BUILD_NAME )
- JOB_NAME=( $CI_JOB_NAME )
- export CI_NODE_INDEX=${JOB_NAME[1]}
- export CI_NODE_TOTAL=${JOB_NAME[2]}
- export KNAPSACK_REPORT_PATH=knapsack/rspec_node_${CI_NODE_INDEX}_${CI_NODE_TOTAL}_report.json
......@@ -69,16 +67,18 @@ stages:
- knapsack rspec "--color --format documentation"
artifacts:
expire_in: 31d
when: always
paths:
- knapsack/
- coverage/
- knapsack/
- tmp/capybara/
.spinach-knapsack: &spinach-knapsack
stage: test
<<: *dedicated-runner
<<: *use-db
script:
- JOB_NAME=( $CI_BUILD_NAME )
- JOB_NAME=( $CI_JOB_NAME )
- export CI_NODE_INDEX=${JOB_NAME[1]}
- export CI_NODE_TOTAL=${JOB_NAME[2]}
- export KNAPSACK_REPORT_PATH=knapsack/spinach_node_${CI_NODE_INDEX}_${CI_NODE_TOTAL}_report.json
......@@ -87,9 +87,11 @@ stages:
- knapsack spinach "-r rerun" || retry '[[ -e tmp/spinach-rerun.txt ]] && bundle exec spinach -r rerun $(cat tmp/spinach-rerun.txt)'
artifacts:
expire_in: 31d
when: always
paths:
- knapsack/
- coverage/
- knapsack/
- tmp/capybara/
# Prepare and merge knapsack tests
......@@ -178,7 +180,7 @@ spinach 9 10: *spinach-knapsack
<<: *dedicated-runner
stage: test
script:
- bundle exec $CI_BUILD_NAME
- bundle exec $CI_JOB_NAME
rubocop:
<<: *ruby-static-analysis
......@@ -209,7 +211,7 @@ rake ee_compat_check:
- ee_compat_check/repo/
- vendor/ruby
artifacts:
name: "${CI_BUILD_NAME}_${CI_BUILD_REF_NAME}_${CI_BUILD_REF}"
name: "${CI_JOB_NAME}_${CI_COMIT_REF_NAME}_${CI_COMMIT_SHA}"
when: on_failure
expire_in: 10d
paths:
......@@ -222,6 +224,14 @@ rake db:migrate:reset:
script:
- bundle exec rake db:migrate:reset
rake db:rollback:
stage: test
<<: *use-db
<<: *dedicated-runner
script:
- bundle exec rake db:rollback STEP=120
- bundle exec rake db:migrate
rake db:seed_fu:
stage: test
<<: *use-db
......@@ -320,7 +330,7 @@ migration paths:
- sed -i 's/localhost/redis/g' config/resque.yml
- bundle install --without postgres production --jobs $(nproc) $FLAGS --retry=3
- bundle exec rake db:drop db:create db:schema:load db:seed_fu
- git checkout $CI_BUILD_REF
- git checkout $CI_COMMIT_SHA
- source scripts/prepare_build.sh
- bundle exec rake db:migrate
......@@ -358,7 +368,7 @@ lint:javascript:report:
stage: post-test
before_script: []
script:
- find app/ spec/ -name '*.js' -or -name '*.js.es6' -exec sed --in-place 's|/\* eslint-disable .*\*/||' {} \; # run report over all files
- find app/ spec/ -name '*.js' -exec sed --in-place 's|/\* eslint-disable .*\*/||' {} \; # run report over all files
- yarn run eslint-report || true # ignore exit code
artifacts:
name: eslint-report
......@@ -384,7 +394,6 @@ trigger_docs:
- master@gitlab-org/gitlab-ce
# Notify slack in the end
notify:slack:
stage: post-test
<<: *dedicated-runner
......@@ -392,7 +401,7 @@ notify:slack:
SETUP_DB: "false"
USE_BUNDLE_INSTALL: "false"
script:
- ./scripts/notify_slack.sh "#development" "Build on \`$CI_BUILD_REF_NAME\` failed! Commit \`$(git log -1 --oneline)\` See <https://gitlab.com/gitlab-org/$(basename "$PWD")/commit/"$CI_BUILD_REF"/pipelines>"
- ./scripts/notify_slack.sh "#development" "Build on \`$CI_COMMIT_REF_NAME\` failed! Commit \`$(git log -1 --oneline)\` See <https://gitlab.com/gitlab-org/$(basename "$PWD")/commit/"$CI_COMMIT_SHA"/pipelines>"
when: on_failure
only:
- master@gitlab-org/gitlab-ce
......
......@@ -78,6 +78,13 @@ towards getting your issue resolved.
Issues and merge requests should be in English and contain appropriate language
for audiences of all ages.
If a contributor is no longer actively working on a submitted merge request
we can decide that the merge request will be finished by one of our
[Merge request coaches][team] or close the merge request. We make this decision
based on how important the change is for our product vision. If a Merge request
coach is going to finish the merge request we assign the
~"coach will finish" label.
## Helping others
Please help other GitLab users when you can. The channels people will reach out
......@@ -399,6 +406,12 @@ There are a few rules to get your merge request accepted:
1. Contains functionality we think other users will benefit from too
1. Doesn't add configuration options or settings options since they complicate
making and testing future changes
1. Changes do not adversely degrade performance.
- Avoid repeated polling of endpoints that require a significant amount of overhead
- Check for N+1 queries via the SQL log or [`QueryRecorder`](https://docs.gitlab.com/ce/development/merge_request_performance_guidelines.html)
- Avoid repeated access of filesystem
1. If you need polling to support real-time features, please use
[polling with ETag caching][polling-etag].
1. Changes after submitting the merge request should be in separate commits
(no squashing). If necessary, you will be asked to squash when the review is
over, before merging.
......@@ -434,6 +447,7 @@ the feature you contribute through all of these steps.
1. Description explaining the relevancy (see following item)
1. Working and clean code that is commented where needed
1. Unit and integration tests that pass on the CI server
1. Performance/scalability implications have been considered, addressed, and tested
1. [Documented][doc-styleguide] in the /doc directory
1. Changelog entry added
1. Reviewed and any concerns are addressed
......@@ -540,6 +554,7 @@ available at [http://contributor-covenant.org/version/1/1/0/](http://contributor
[UX Guide for GitLab]: http://docs.gitlab.com/ce/development/ux_guide/
[license-finder-doc]: doc/development/licensing.md
[GitLab Inc engineering workflow]: https://about.gitlab.com/handbook/engineering/workflow/#labelling-issues
[polling-etag]: https://docs.gitlab.com/ce/development/polling.html
[^1]: Specs other than JavaScript specs are considered backend code. Haml
changes are considered backend code if they include Ruby code other than just
......
/* eslint-disable no-param-reassign */
const MAX_MESSAGE_LENGTH = 500;
const MESSAGE_CELL_SELECTOR = '.abuse-reports .message';
((global) => {
const MAX_MESSAGE_LENGTH = 500;
const MESSAGE_CELL_SELECTOR = '.abuse-reports .message';
class AbuseReports {
class AbuseReports {
constructor() {
$(MESSAGE_CELL_SELECTOR).each(this.truncateLongMessage);
$(document)
......@@ -18,7 +15,7 @@
if (reportMessage.length > MAX_MESSAGE_LENGTH) {
$messageCellElement.data('original-message', reportMessage);
$messageCellElement.data('message-truncated', 'true');
$messageCellElement.text(global.text.truncate(reportMessage, MAX_MESSAGE_LENGTH));
$messageCellElement.text(window.gl.text.truncate(reportMessage, MAX_MESSAGE_LENGTH));
}
}
......@@ -34,7 +31,7 @@
$messageCellElement.text(`${originalMessage.substr(0, (MAX_MESSAGE_LENGTH - 3))}...`);
}
}
}
}
global.AbuseReports = AbuseReports;
})(window.gl || (window.gl = {}));
window.gl = window.gl || {};
window.gl.AbuseReports = AbuseReports;
......@@ -2,8 +2,7 @@
/* global Pager */
/* global Cookies */
((global) => {
class Activities {
class Activities {
constructor() {
Pager.init(20, true, false, this.updateTooltips);
$('.event-filter-link').on('click', (e) => {
......@@ -31,7 +30,7 @@
$sender.closest('li').toggleClass('active');
}
}
}
global.Activities = Activities;
})(window.gl || (window.gl = {}));
window.gl = window.gl || {};
window.gl.Activities = Activities;
/* eslint-disable func-names, space-before-function-paren, wrap-iife, one-var, no-var, one-var-declaration-per-line, no-unused-vars, no-else-return, prefer-arrow-callback, camelcase, quotes, comma-dangle, max-len */
(function() {
this.Admin = (function() {
window.Admin = (function() {
function Admin() {
var modal, showBlacklistType;
$('input#user_force_random_password').on('change', function(elem) {
......@@ -60,5 +59,4 @@
}
return Admin;
})();
}).call(window);
})();
/* 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 */
(function() {
var Api = {
var Api = {
groupsPath: "/api/:version/groups.json",
groupPath: "/api/:version/groups/:id.json",
namespacesPath: "/api/:version/namespaces.json",
......@@ -52,15 +51,15 @@
});
},
// Return projects list. Filtered by query
projects: function(query, order, callback) {
projects: function(query, options, callback) {
var url = Api.buildUrl(Api.projectsPath);
return $.ajax({
url: url,
data: {
data: $.extend({
search: query,
order_by: order,
per_page: 20
},
per_page: 20,
membership: true
}, options),
dataType: "json"
}).done(function(projects) {
return callback(projects);
......@@ -144,7 +143,6 @@
}
return url.replace(':version', gon.api_version);
}
};
};
window.Api = Api;
}).call(window);
window.Api = Api;
/* eslint-disable func-names, space-before-function-paren, wrap-iife, quotes, prefer-arrow-callback, no-var, one-var, one-var-declaration-per-line, no-else-return, max-len */
(function() {
this.Aside = (function() {
window.Aside = (function() {
function Aside() {
$(document).off("click", "a.show-aside");
$(document).on("click", 'a.show-aside', function(e) {
......@@ -21,5 +21,4 @@
}
return Aside;
})();
}).call(window);
})();
/* eslint-disable func-names, space-before-function-paren, wrap-iife, no-param-reassign, quotes, prefer-template, no-var, one-var, no-unused-vars, one-var-declaration-per-line, no-void, consistent-return, no-empty, max-len */
(function() {
this.Autosave = (function() {
window.Autosave = (function() {
function Autosave(field, key) {
this.field = field;
if (key.join != null) {
......@@ -58,5 +58,4 @@
};
return Autosave;
})();
}).call(window);
})();
......@@ -3,6 +3,7 @@
import emojiMap from 'emojis/digests.json';
import emojiAliases from 'emojis/aliases.json';
import { glEmojiTag } from './behaviors/gl_emoji';
import isEmojiNameValid from './behaviors/gl_emoji/is_emoji_name_valid';
const animationEndEventString = 'animationend webkitAnimationEnd MSAnimationEnd oAnimationEnd';
const requestAnimationFrame = window.requestAnimationFrame ||
......@@ -454,14 +455,21 @@ AwardsHandler.prototype.normalizeEmojiName = function normalizeEmojiName(emoji)
AwardsHandler
.prototype
.addEmojiToFrequentlyUsedList = function addEmojiToFrequentlyUsedList(emoji) {
const frequentlyUsedEmojis = this.getFrequentlyUsedEmojis();
frequentlyUsedEmojis.push(emoji);
Cookies.set('frequently_used_emojis', frequentlyUsedEmojis.join(','), { expires: 365 });
if (isEmojiNameValid(emoji)) {
this.frequentlyUsedEmojis = _.uniq(this.getFrequentlyUsedEmojis().concat(emoji));
Cookies.set('frequently_used_emojis', this.frequentlyUsedEmojis.join(','), { expires: 365 });
}
};
AwardsHandler.prototype.getFrequentlyUsedEmojis = function getFrequentlyUsedEmojis() {
const frequentlyUsedEmojis = (Cookies.get('frequently_used_emojis') || '').split(',');
return _.compact(_.uniq(frequentlyUsedEmojis));
return this.frequentlyUsedEmojis || (() => {
const frequentlyUsedEmojis = _.uniq((Cookies.get('frequently_used_emojis') || '').split(','));
this.frequentlyUsedEmojis = frequentlyUsedEmojis.filter(
inputName => isEmojiNameValid(inputName),
);
return this.frequentlyUsedEmojis;
})();
};
AwardsHandler.prototype.setupSearch = function setupSearch() {
......
......@@ -13,9 +13,14 @@ function emojiImageTag(name, src) {
}
function assembleFallbackImageSrc(inputName) {
const name = Object.prototype.hasOwnProperty.call(emojiAliases, inputName) ?
let name = Object.prototype.hasOwnProperty.call(emojiAliases, inputName) ?
emojiAliases[inputName] : inputName;
const emojiInfo = emojiMap[name];
let emojiInfo = emojiMap[name];
// Fallback to question mark for unknown emojis
if (!emojiInfo) {
name = 'grey_question';
emojiInfo = emojiMap[name];
}
const fallbackImageSrc = `${gon.asset_host || ''}${gon.relative_url_root || ''}/assets/emoji/${name}-${emojiInfo.digest}.png`;
return fallbackImageSrc;
......@@ -26,9 +31,15 @@ const glEmojiTagDefaults = {
};
function glEmojiTag(inputName, options) {
const opts = Object.assign({}, glEmojiTagDefaults, options);
const name = Object.prototype.hasOwnProperty.call(emojiAliases, inputName) ?
let name = Object.prototype.hasOwnProperty.call(emojiAliases, inputName) ?
emojiAliases[inputName] : inputName;
const emojiInfo = emojiMap[name];
let emojiInfo = emojiMap[name];
// Fallback to question mark for unknown emojis
if (!emojiInfo) {
name = 'grey_question';
emojiInfo = emojiMap[name];
}
const fallbackImageSrc = assembleFallbackImageSrc(name);
const fallbackSpriteClass = `emoji-${name}`;
......
import emojiMap from 'emojis/digests.json';
import emojiAliases from 'emojis/aliases.json';
function isEmojiNameValid(inputName) {
const name = Object.prototype.hasOwnProperty.call(emojiAliases, inputName) ?
emojiAliases[inputName] : inputName;
return name && emojiMap[name];
}
export default isEmojiNameValid;
......@@ -6,7 +6,7 @@
// "Meta+Enter" (Mac) or "Ctrl+Enter" (Linux/Windows) key combination, the form
// is submitted.
//
require('../extensions/jquery');
import '../commons/bootstrap';
//
// ### Example Markup
......
......@@ -4,7 +4,7 @@
// When called on a form with input fields with the `required` attribute, the
// form's submit button will be disabled until all required fields have values.
//
require('../extensions/jquery');
import '../commons/bootstrap';
//
// ### Example Markup
......
......@@ -21,8 +21,13 @@
// %a.js-toggle-button
// %div.js-toggle-content
//
$('body').on('click', '.js-toggle-button', function() {
$('body').on('click', '.js-toggle-button', function(e) {
toggleContainer($(this).closest('.js-toggle-container'));
const targetTag = e.target.tagName.toLowerCase();
if (targetTag === 'a' || targetTag === 'button') {
e.preventDefault();
}
});
// If we're accessing a permalink, ensure it is not inside a
......
const lineNumberRe = /^L[0-9]+/;
const updateLineNumbersOnBlobPermalinks = (linksToUpdate) => {
const hash = gl.utils.getLocationHash();
if (hash && lineNumberRe.test(hash)) {
const hashUrlString = `#${hash}`;
[].concat(Array.prototype.slice.call(linksToUpdate)).forEach((permalinkButton) => {
const baseHref = permalinkButton.getAttribute('data-original-href') || (() => {
const href = permalinkButton.getAttribute('href');
permalinkButton.setAttribute('data-original-href', href);
return href;
})();
permalinkButton.setAttribute('href', `${baseHref}${hashUrlString}`);
});
}
};
function BlobLinePermalinkUpdater(blobContentHolder, lineNumberSelector, elementsToUpdate) {
const updateBlameAndBlobPermalinkCb = () => {
// Wait for the hash to update from the LineHighlighter callback
setTimeout(() => {
updateLineNumbersOnBlobPermalinks(elementsToUpdate);
}, 0);
};
blobContentHolder.addEventListener('click', (e) => {
if (e.target.matches(lineNumberSelector)) {
updateBlameAndBlobPermalinkCb();
}
});
updateBlameAndBlobPermalinkCb();
}
export default BlobLinePermalinkUpdater;
......@@ -2,6 +2,9 @@
/* global Vue */
/* global BoardService */
import FilteredSearchBoards from './filtered_search_boards';
import eventHub from './eventhub';
window.Vue = require('vue');
window.Vue.use(require('vue-resource'));
require('./models/issue');
......@@ -59,6 +62,14 @@ $(() => {
},
created () {
gl.boardService = new BoardService(this.endpoint, this.bulkUpdatePath, this.boardId);
this.filterManager = new FilteredSearchBoards(Store.filter, true);
// Listen for updateTokens event
eventHub.$on('updateTokens', this.updateTokens);
},
beforeDestroy() {
eventHub.$off('updateTokens', this.updateTokens);
},
mounted () {
Store.disabled = this.disabled;
......@@ -77,11 +88,16 @@ $(() => {
Store.addBlankState();
this.loading = false;
});
},
methods: {
updateTokens() {
this.filterManager.updateTokens();
}
},
});
gl.IssueBoardsSearch = new Vue({
el: document.getElementById('js-boards-search'),
el: document.getElementById('js-add-list'),
data: {
filters: Store.state.filters
},
......
......@@ -28,16 +28,16 @@ require('./board_list');
data () {
return {
detailIssue: Store.detail,
filters: Store.state.filters,
filter: Store.filter,
};
},
watch: {
filters: {
handler () {
filter: {
handler() {
this.list.page = 1;
this.list.getIssues(true);
},
deep: true
deep: true,
},
detailIssue: {
handler () {
......
......@@ -17,7 +17,8 @@ export default {
:list="list"
:issue="issue"
:issue-link-base="issueLinkBase"
:root-path="rootPath" />
:root-path="rootPath"
:update-filters="true" />
</li>
`,
components: {
......
/* global Vue */
import eventHub from '../eventhub';
(() => {
const Store = gl.issueBoards.BoardsStore;
......@@ -23,6 +25,11 @@
type: String,
required: true,
},
updateFilters: {
type: Boolean,
required: false,
default: false,
},
},
methods: {
showLabel(label) {
......@@ -31,29 +38,25 @@
return !this.list.label || label.id !== this.list.label.id;
},
filterByLabel(label, e) {
let labelToggleText = label.title;
const labelIndex = Store.state.filters.label_name.indexOf(label.title);
if (!this.updateFilters) return;
const filterPath = gl.issueBoards.BoardsStore.filter.path.split('&');
const labelTitle = encodeURIComponent(label.title);
const param = `label_name[]=${labelTitle}`;
const labelIndex = filterPath.indexOf(param);
$(e.currentTarget).tooltip('hide');
if (labelIndex === -1) {
Store.state.filters.label_name.push(label.title);
$('.labels-filter').prepend(`<input type="hidden" name="label_name[]" value="${label.title}" />`);
filterPath.push(param);
} else {
Store.state.filters.label_name.splice(labelIndex, 1);
labelToggleText = Store.state.filters.label_name[0];
$(`.labels-filter input[name="label_name[]"][value="${label.title}"]`).remove();
filterPath.splice(labelIndex, 1);
}
const selectedLabels = Store.state.filters.label_name;
if (selectedLabels.length === 0) {
labelToggleText = 'Label';
} else if (selectedLabels.length > 1) {
labelToggleText = `${selectedLabels[0]} + ${selectedLabels.length - 1} more`;
}
$('.labels-filter .dropdown-toggle-text').text(labelToggleText);
gl.issueBoards.BoardsStore.filter.path = filterPath.join('&');
Store.updateFiltersUrl();
eventHub.$emit('updateTokens');
},
labelStyle(label) {
return {
......
/* global Vue */
const userFilter = require('./filters/user');
const milestoneFilter = require('./filters/milestone');
const labelFilter = require('./filters/label');
import FilteredSearchBoards from '../../filtered_search_boards';
import FilteredSearchContainer from '../../../filtered_search/container';
module.exports = Vue.extend({
export default {
name: 'modal-filters',
props: {
projectId: {
type: Number,
store: {
type: Object,
required: true,
},
milestonePath: {
type: String,
required: true,
},
labelPath: {
type: String,
required: true,
},
mounted() {
FilteredSearchContainer.container = this.$el;
this.filteredSearch = new FilteredSearchBoards(this.store);
this.filteredSearch.removeTokens();
},
destroyed() {
gl.issueBoards.ModalStore.setDefaultFilter();
},
components: {
userFilter,
milestoneFilter,
labelFilter,
beforeDestroy() {
this.filteredSearch.cleanup();
FilteredSearchContainer.container = document;
this.store.path = '';
},
template: `
<div class="modal-filters">
<user-filter
dropdown-class-name="dropdown-menu-author"
toggle-class-name="js-user-search js-author-search"
toggle-label="Author"
field-name="author_id"
:project-id="projectId"></user-filter>
<user-filter
dropdown-class-name="dropdown-menu-author"
toggle-class-name="js-assignee-search"
toggle-label="Assignee"
field-name="assignee_id"
:null-user="true"
:project-id="projectId"></user-filter>
<milestone-filter :milestone-path="milestonePath"></milestone-filter>
<label-filter :label-path="labelPath"></label-filter>
</div>
`,
});
template: '#js-board-modal-filter',
};
/* eslint-disable no-new */
/* global Vue */
/* global LabelsSelect */
module.exports = Vue.extend({
name: 'filter-label',
props: {
labelPath: {
type: String,
required: true,
},
},
mounted() {
new LabelsSelect(this.$refs.dropdown);
},
template: `
<div class="dropdown">
<button
class="dropdown-menu-toggle js-label-select js-multiselect js-extra-options"
type="button"
data-toggle="dropdown"
data-show-any="true"
data-show-no="true"
:data-labels="labelPath"
ref="dropdown">
<span class="dropdown-toggle-text">
Label
</span>
<i class="fa fa-chevron-down"></i>
</button>
<div class="dropdown-menu dropdown-select dropdown-menu-paging dropdown-menu-labels dropdown-menu-selectable">
<div class="dropdown-title">
Filter by label
<button
class="dropdown-title-button dropdown-menu-close"
aria-label="Close"
type="button">
<i class="fa fa-times dropdown-menu-close-icon"></i>
</button>
</div>
<div class="dropdown-input">
<input
type="search"
class="dropdown-input-field"
placeholder="Search"
autocomplete="off" />
<i class="fa fa-search dropdown-input-search"></i>
<i role="button" class="fa fa-times dropdown-input-clear js-dropdown-input-clear"></i>
</div>
<div class="dropdown-content"></div>
<div class="dropdown-loading"><i class="fa fa-spinner fa-spin"></i></div>
</div>
</div>
`,
});
/* eslint-disable no-new */
/* global Vue */
/* global MilestoneSelect */
module.exports = Vue.extend({
name: 'filter-milestone',
props: {
milestonePath: {
type: String,
required: true,
},
},
mounted() {
new MilestoneSelect(null, this.$refs.dropdown);
},
template: `
<div class="dropdown">
<button
class="dropdown-menu-toggle js-milestone-select"
type="button"
data-toggle="dropdown"
data-show-any="true"
data-show-upcoming="true"
data-field-name="milestone_title"
:data-milestones="milestonePath"
ref="dropdown">
<span class="dropdown-toggle-text">
Milestone
</span>
<i class="fa fa-chevron-down"></i>
</button>
<div class="dropdown-menu dropdown-select dropdown-menu-selectable dropdown-menu-milestone">
<div class="dropdown-title">
<span>Filter by milestone</span>
<button
class="dropdown-title-button dropdown-menu-close"
aria-label="Close"
type="button">
<i class="fa fa-times dropdown-menu-close-icon"></i>
</button>
</div>
<div class="dropdown-input">
<input
type="search"
class="dropdown-input-field"
placeholder="Search milestones"
autocomplete="off" />
<i class="fa fa-search dropdown-input-search"></i>
<i role="button" class="fa fa-times dropdown-input-clear js-dropdown-input-clear"></i>
</div>
<div class="dropdown-content"></div>
<div class="dropdown-loading"><i class="fa fa-spinner fa-spin"></i></div>
</div>
</div>
`,
});
/* eslint-disable no-new */
/* global Vue */
/* global UsersSelect */
module.exports = Vue.extend({
name: 'filter-user',
props: {
toggleClassName: {
type: String,
required: true,
},
dropdownClassName: {
type: String,
required: false,
default: '',
},
toggleLabel: {
type: String,
required: true,
},
fieldName: {
type: String,
required: true,
},
nullUser: {
type: Boolean,
required: false,
default: false,
},
projectId: {
type: Number,
required: true,
},
},
mounted() {
new UsersSelect(null, this.$refs.dropdown);
},
computed: {
currentUsername() {
return gon.current_username;
},
dropdownTitle() {
return `Filter by ${this.toggleLabel.toLowerCase()}`;
},
inputPlaceholder() {
return `Search ${this.toggleLabel.toLowerCase()}`;
},
},
template: `
<div class="dropdown">
<button
class="dropdown-menu-toggle js-user-search"
:class="toggleClassName"
type="button"
data-toggle="dropdown"
data-current-user="true"
:data-any-user="'Any ' + toggleLabel"
:data-null-user="nullUser"
:data-field-name="fieldName"
:data-project-id="projectId"
:data-first-user="currentUsername"
ref="dropdown">
<span class="dropdown-toggle-text">
{{ toggleLabel }}
</span>
<i class="fa fa-chevron-down"></i>
</button>
<div
class="dropdown-menu dropdown-select dropdown-menu-user dropdown-menu-selectable"
:class="dropdownClassName">
<div class="dropdown-title">
{{ dropdownTitle }}
<button
class="dropdown-title-button dropdown-menu-close"
aria-label="Close"
type="button">
<i class="fa fa-times dropdown-menu-close-icon"></i>
</button>
</div>
<div class="dropdown-input">
<input
type="search"
class="dropdown-input-field"
autocomplete="off"
:placeholder="inputPlaceholder" />
<i class="fa fa-search dropdown-input-search"></i>
<i
role="button"
class="fa fa-times dropdown-input-clear js-dropdown-input-clear">
</i>
</div>
<div class="dropdown-content"></div>
<div class="dropdown-loading"><i class="fa fa-spinner fa-spin"></i></div>
</div>
</div>
`,
});
/* global Vue */
import Vue from 'vue';
import modalFilters from './filters';
require('./tabs');
const modalFilters = require('./filters');
(() => {
const ModalStore = gl.issueBoards.ModalStore;
......@@ -66,16 +67,7 @@ const modalFilters = require('./filters');
<div
class="add-issues-search append-bottom-10"
v-if="showSearch">
<modal-filters
:project-id="projectId"
:milestone-path="milestonePath"
:label-path="labelPath">
</modal-filters>
<input
placeholder="Search issues..."
class="form-control"
type="search"
v-model="searchTerm" />
<modal-filters :store="filter" />
<button
type="button"
class="btn btn-success btn-inverted prepend-left-10"
......
/* global Vue */
/* global ListIssue */
import queryData from '../../utils/query_data';
require('./header');
require('./list');
......@@ -47,9 +48,6 @@ require('./empty_state');
page() {
this.loadIssues();
},
searchTerm() {
this.searchOperation();
},
showAddIssuesModal() {
if (this.showAddIssuesModal && !this.issues.length) {
this.loading = true;
......@@ -72,19 +70,13 @@ require('./empty_state');
},
},
methods: {
searchOperation: _.debounce(function searchOperationDebounce() {
this.loadIssues(true);
}, 500),
loadIssues(clearIssues = false) {
if (!this.showAddIssuesModal) return false;
const queryData = Object.assign({}, this.filter, {
search: this.searchTerm,
return gl.boardService.getBacklog(queryData(this.filter.path, {
page: this.page,
per: this.perPage,
});
return gl.boardService.getBacklog(queryData).then((res) => {
})).then((res) => {
const data = res.json();
if (clearIssues) {
......
import Vue from 'vue';
export default new Vue();
/* eslint-disable class-methods-use-this */
import FilteredSearchContainer from '../filtered_search/container';
export default class FilteredSearchBoards extends gl.FilteredSearchManager {
constructor(store, updateUrl = false) {
super('boards');
this.store = store;
this.updateUrl = updateUrl;
// Issue boards is slightly different, we handle all the requests async
// instead or reloading the page, we just re-fire the list ajax requests
this.isHandledAsync = true;
}
updateObject(path) {
this.store.path = path.substr(1);
if (this.updateUrl) {
gl.issueBoards.BoardsStore.updateFiltersUrl();
}
}
removeTokens() {
const tokens = FilteredSearchContainer.container.querySelectorAll('.js-visual-token');
// Remove all the tokens as they will be replaced by the search manager
[].forEach.call(tokens, (el) => {
el.parentNode.removeChild(el);
});
}
updateTokens() {
this.removeTokens();
this.loadSearchParamsFromURL();
// Get the placeholder back if search is empty
this.filteredSearchInput.dispatchEvent(new Event('input'));
}
}
/* eslint-disable space-before-function-paren, no-underscore-dangle, class-methods-use-this, consistent-return, no-shadow, no-param-reassign, max-len, no-unused-vars */
/* global ListIssue */
/* global ListLabel */
import queryData from '../utils/query_data';
class List {
constructor (obj) {
......@@ -10,7 +11,6 @@ class List {
this.title = obj.title;
this.type = obj.list_type;
this.preset = ['done', 'blank'].indexOf(this.type) > -1;
this.filters = gl.issueBoards.BoardsStore.state.filters;
this.page = 1;
this.loading = true;
this.loadingMore = false;
......@@ -65,12 +65,9 @@ class List {
}
getIssues (emptyIssues = true) {
const filters = this.filters;
const data = { page: this.page };
const data = queryData(gl.issueBoards.BoardsStore.filter.path, { page: this.page });
Object.keys(filters).forEach((key) => { data[key] = filters[key]; });
if (this.label) {
if (this.label && data.label_name) {
data.label_name = data.label_name.filter(label => label !== this.label.title);
}
......
......@@ -8,6 +8,9 @@
gl.issueBoards.BoardsStore = {
disabled: false,
filter: {
path: '',
},
state: {},
detail: {
issue: {}
......@@ -18,13 +21,7 @@
},
create () {
this.state.lists = [];
this.state.filters = {
author_id: gl.utils.getParameterValues('author_id')[0],
assignee_id: gl.utils.getParameterValues('assignee_id')[0],
milestone_title: gl.utils.getParameterValues('milestone_title')[0],
label_name: gl.utils.getParameterValues('label_name[]'),
search: ''
};
this.filter.path = gl.utils.getUrlParamsArray().join('&');
},
addList (listObj) {
const list = new List(listObj);
......@@ -123,7 +120,7 @@
})[0];
},
updateFiltersUrl () {
history.pushState(null, null, `?${$.param(this.state.filters)}`);
history.pushState(null, null, `?${this.filter.path}`);
}
};
})();
......@@ -17,17 +17,9 @@
loadingNewPage: false,
page: 1,
perPage: 50,
};
this.setDefaultFilter();
}
setDefaultFilter() {
this.store.filter = {
author_id: '',
assignee_id: '',
milestone_title: '',
label_name: [],
filter: {
path: '',
},
};
}
......
export default (path, extraData) => path.split('&').reduce((dataParam, filterParam) => {
if (filterParam === '') return dataParam;
const data = dataParam;
const paramSplit = filterParam.split('=');
const paramKeyNormalized = paramSplit[0].replace('[]', '');
const isArray = paramSplit[0].indexOf('[]');
const value = decodeURIComponent(paramSplit[1]).replace(/\+/g, ' ');
if (isArray !== -1) {
if (!data[paramKeyNormalized]) {
data[paramKeyNormalized] = [];
}
data[paramKeyNormalized].push(value);
} else {
data[paramKeyNormalized] = value;
}
return data;
}, extraData);
/* eslint-disable func-names, space-before-function-paren, wrap-iife, one-var, no-var, one-var-declaration-per-line, quotes, no-shadow, prefer-arrow-callback, prefer-template, consistent-return, no-return-assign, new-parens, no-param-reassign, max-len */
(function() {
var Breakpoints = (function() {
var Breakpoints = (function() {
var BreakpointInstance, instance;
function Breakpoints() {}
......@@ -60,13 +59,8 @@
};
return Breakpoints;
})();
})();
$((function(_this) {
return function() {
return _this.bp = Breakpoints.get();
};
})(this));
$(() => { window.bp = Breakpoints.get(); });
window.Breakpoints = Breakpoints;
}).call(window);
window.Breakpoints = Breakpoints;
/* eslint-disable func-names, space-before-function-paren, prefer-arrow-callback, no-var, quotes, no-else-return, object-shorthand, comma-dangle, max-len */
(function() {
$(function() {
$(function() {
var previewPath;
$('input#broadcast_message_color').on('input', function() {
var previewColor;
......@@ -30,5 +30,4 @@
});
}
});
});
}).call(window);
});
/* eslint-disable func-names, space-before-function-paren, no-var, prefer-rest-params, wrap-iife, no-use-before-define, no-param-reassign, quotes, yoda, no-else-return, consistent-return, comma-dangle, object-shorthand, prefer-template, one-var, one-var-declaration-per-line, no-unused-vars, max-len, vars-on-top */
/* global Breakpoints */
(function() {
var bind = function(fn, me) { return function() { return fn.apply(me, arguments); }; };
var AUTO_SCROLL_OFFSET = 75;
var DOWN_BUILD_TRACE = '#down-build-trace';
var bind = function(fn, me) { return function() { return fn.apply(me, arguments); }; };
var AUTO_SCROLL_OFFSET = 75;
var DOWN_BUILD_TRACE = '#down-build-trace';
this.Build = (function() {
window.Build = (function() {
Build.timeout = null;
Build.state = null;
......@@ -281,5 +280,4 @@
};
return Build;
})();
}).call(window);
})();
/* eslint-disable func-names, space-before-function-paren, wrap-iife, prefer-arrow-callback, no-unused-vars, no-return-assign, max-len */
(function() {
this.BuildArtifacts = (function() {
window.BuildArtifacts = (function() {
function BuildArtifacts() {
this.disablePropagation();
this.setupEntryClick();
......@@ -22,5 +22,4 @@
};
return BuildArtifacts;
})();
}).call(window);
})();
(() => {
window.gl = window.gl || {};
class CILintEditor {
window.gl = window.gl || {};
class CILintEditor {
constructor() {
this.editor = window.ace.edit('ci-editor');
this.textarea = document.querySelector('#content');
......@@ -12,7 +12,6 @@
this.textarea.value = content;
});
}
}
}
gl.CILintEditor = CILintEditor;
})();
gl.CILintEditor = CILintEditor;
/* eslint-disable func-names, space-before-function-paren, wrap-iife */
/* global CommitFile */
(function() {
this.Commit = (function() {
window.Commit = (function() {
function Commit() {
$('.files .diff-file').each(function() {
return new CommitFile(this);
......@@ -10,5 +9,4 @@
}
return Commit;
})();
}).call(window);
})();
/* eslint-disable func-names, space-before-function-paren, wrap-iife, quotes, consistent-return, no-return-assign, no-param-reassign, one-var, no-var, one-var-declaration-per-line, no-unused-vars, prefer-template, object-shorthand, comma-dangle, max-len, prefer-arrow-callback */
/* global Pager */
(function() {
this.CommitsList = (function() {
window.CommitsList = (function() {
var CommitsList = {};
CommitsList.timer = null;
......@@ -64,5 +63,4 @@
};
return CommitsList;
})();
}).call(window);
})();
import 'jquery';
import $ from 'jquery';
// bootstrap jQuery plugins
import 'bootstrap-sass/assets/javascripts/bootstrap/affix';
......@@ -8,3 +8,9 @@ import 'bootstrap-sass/assets/javascripts/bootstrap/modal';
import 'bootstrap-sass/assets/javascripts/bootstrap/tab';
import 'bootstrap-sass/assets/javascripts/bootstrap/transition';
import 'bootstrap-sass/assets/javascripts/bootstrap/tooltip';
// custom jQuery functions
$.fn.extend({
disable() { return $(this).attr('disabled', 'disabled').addClass('disabled'); },
enable() { return $(this).removeAttr('disabled').removeClass('disabled'); },
});
import './polyfills';
import './jquery';
import './bootstrap';
// ECMAScript polyfills
import 'core-js/fn/array/find';
import 'core-js/fn/object/assign';
import 'core-js/fn/promise';
import 'core-js/fn/string/code-point-at';
import 'core-js/fn/string/from-code-point';
// Browser polyfills
import './polyfills/custom_event';
import './polyfills/element';
if (typeof window.CustomEvent !== 'function') {
window.CustomEvent = function CustomEvent(event, params) {
const evt = document.createEvent('CustomEvent');
const evtParams = params || { bubbles: false, cancelable: false, detail: undefined };
evt.initCustomEvent(event, evtParams.bubbles, evtParams.cancelable, evtParams.detail);
return evt;
};
window.CustomEvent.prototype = Event;
}
/* global Element */
/* eslint-disable consistent-return, max-len, no-empty, func-names */
Element.prototype.closest = Element.prototype.closest || function closest(selector, selectedElement = this) {
if (!selectedElement) return;
return selectedElement.matches(selector) ? selectedElement : Element.prototype.closest(selector, selectedElement.parentElement);
};
Element.prototype.closest = Element.prototype.closest ||
function closest(selector, selectedElement = this) {
if (!selectedElement) return null;
return selectedElement.matches(selector) ?
selectedElement :
Element.prototype.closest(selector, selectedElement.parentElement);
};
Element.prototype.matches = Element.prototype.matches ||
Element.prototype.matchesSelector ||
......@@ -12,9 +12,9 @@ Element.prototype.matches = Element.prototype.matches ||
Element.prototype.msMatchesSelector ||
Element.prototype.oMatchesSelector ||
Element.prototype.webkitMatchesSelector ||
function (s) {
const matches = (this.document || this.ownerDocument).querySelectorAll(s);
let i = matches.length - 1;
while (i >= 0 && matches.item(i) !== this) { i -= 1; }
function matches(selector) {
const elms = (this.document || this.ownerDocument).querySelectorAll(selector);
let i = elms.length - 1;
while (i >= 0 && elms.item(i) !== this) { i -= 1; }
return i > -1;
};
/* eslint-disable func-names, space-before-function-paren, wrap-iife, quotes, no-var, object-shorthand, consistent-return, no-unused-vars, comma-dangle, vars-on-top, prefer-template, max-len */
(function() {
this.Compare = (function() {
window.Compare = (function() {
function Compare(opts) {
this.opts = opts;
this.source_loading = $(".js-source-loading");
......@@ -87,5 +87,4 @@
};
return Compare;
})();
}).call(window);
})();
/* eslint-disable func-names, space-before-function-paren, one-var, no-var, one-var-declaration-per-line, object-shorthand, comma-dangle, prefer-arrow-callback, no-else-return, newline-per-chained-call, wrap-iife, max-len */
(function() {
this.CompareAutocomplete = (function() {
window.CompareAutocomplete = (function() {
function CompareAutocomplete() {
this.initDropdown();
}
......@@ -19,7 +18,8 @@
return $.ajax({
url: $dropdown.data('refs-url'),
data: {
ref: $dropdown.data('ref')
ref: $dropdown.data('ref'),
search: term,
}
}).done(function(refs) {
return callback(refs);
......@@ -27,7 +27,7 @@
},
selectable: true,
filterable: true,
filterByText: true,
filterRemote: true,
fieldName: $dropdown.data('field-name'),
filterInput: 'input[type="search"]',
renderRow: function(ref) {
......@@ -65,5 +65,4 @@
};
return CompareAutocomplete;
})();
}).call(window);
})();
/* eslint-disable func-names, space-before-function-paren, wrap-iife, one-var, no-var, camelcase, one-var-declaration-per-line, no-else-return, max-len */
(function() {
this.ConfirmDangerModal = (function() {
window.ConfirmDangerModal = (function() {
function ConfirmDangerModal(form, text) {
var project_path, submit;
this.form = form;
......@@ -27,5 +27,4 @@
}
return ConfirmDangerModal;
})();
}).call(window);
})();
/* 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 */
/* jshint esversion: 6 */
require('./lib/utils/common_utils');
(() => {
const gfmRules = {
const gfmRules = {
// The filters referenced in lib/banzai/pipeline/gfm_pipeline.rb convert
// GitLab Flavored Markdown (GFM) to HTML.
// These handlers consequently convert that same HTML to GFM to be copied to the clipboard.
......@@ -120,10 +118,10 @@ require('./lib/utils/common_utils');
},
SyntaxHighlightFilter: {
'pre.code.highlight'(el, t) {
const text = t.trim();
const text = t.trimRight();
let lang = el.getAttribute('lang');
if (lang === 'plaintext') {
if (!lang || lang === 'plaintext') {
lang = '';
}
......@@ -159,7 +157,7 @@ require('./lib/utils/common_utils');
const backticks = Array(backtickCount + 1).join('`');
const spaceOrNoSpace = backtickCount > 1 ? ' ' : '';
return backticks + spaceOrNoSpace + text + spaceOrNoSpace + backticks;
return backticks + spaceOrNoSpace + text.trim() + spaceOrNoSpace + backticks;
},
'blockquote'(el, text) {
return text.trim().split('\n').map(s => `> ${s}`.trim()).join('\n');
......@@ -271,32 +269,33 @@ require('./lib/utils/common_utils');
return `| ${cells.join(' | ')} |`;
},
},
};
};
class CopyAsGFM {
class CopyAsGFM {
constructor() {
$(document).on('copy', '.md, .wiki', this.handleCopy);
$(document).on('paste', '.js-gfm-input', this.handlePaste);
$(document).on('copy', '.md, .wiki', (e) => { this.copyAsGFM(e, CopyAsGFM.transformGFMSelection); });
$(document).on('copy', 'pre.code.highlight, .diff-content .line_content', (e) => { this.copyAsGFM(e, CopyAsGFM.transformCodeSelection); });
$(document).on('paste', '.js-gfm-input', this.pasteGFM.bind(this));
}
handleCopy(e) {
copyAsGFM(e, transformer) {
const clipboardData = e.originalEvent.clipboardData;
if (!clipboardData) return;
const documentFragment = window.gl.utils.getSelectedFragment();
if (!documentFragment) return;
// If the documentFragment contains more than just Markdown, don't copy as GFM.
if (documentFragment.querySelector('.md, .wiki')) return;
const el = transformer(documentFragment.cloneNode(true));
if (!el) return;
e.preventDefault();
clipboardData.setData('text/plain', documentFragment.textContent);
e.stopPropagation();
const gfm = CopyAsGFM.nodeToGFM(documentFragment);
clipboardData.setData('text/x-gfm', gfm);
clipboardData.setData('text/plain', el.textContent);
clipboardData.setData('text/x-gfm', CopyAsGFM.nodeToGFM(el));
}
handlePaste(e) {
pasteGFM(e) {
const clipboardData = e.originalEvent.clipboardData;
if (!clipboardData) return;
......@@ -308,7 +307,47 @@ require('./lib/utils/common_utils');
window.gl.utils.insertText(e.target, gfm);
}
static transformGFMSelection(documentFragment) {
// If the documentFragment contains more than just Markdown, don't copy as GFM.
if (documentFragment.querySelector('.md, .wiki')) return null;
return documentFragment;
}
static transformCodeSelection(documentFragment) {
const lineEls = documentFragment.querySelectorAll('.line');
let codeEl;
if (lineEls.length > 1) {
codeEl = document.createElement('pre');
codeEl.className = 'code highlight';
const lang = lineEls[0].getAttribute('lang');
if (lang) {
codeEl.setAttribute('lang', lang);
}
} else {
codeEl = document.createElement('code');
}
if (lineEls.length > 0) {
for (let i = 0; i < lineEls.length; i += 1) {
const lineEl = lineEls[i];
codeEl.appendChild(lineEl);
codeEl.appendChild(document.createTextNode('\n'));
}
} else {
codeEl.appendChild(documentFragment);
}
return codeEl;
}
static nodeToGFM(node) {
if (node.nodeType === Node.COMMENT_NODE) {
return '';
}
if (node.nodeType === Node.TEXT_NODE) {
return node.textContent;
}
......@@ -355,10 +394,9 @@ require('./lib/utils/common_utils');
return clonedParentNode.innerText || clonedParentNode.textContent;
}
}
}
window.gl = window.gl || {};
window.gl.CopyAsGFM = CopyAsGFM;
window.gl = window.gl || {};
window.gl.CopyAsGFM = CopyAsGFM;
new CopyAsGFM();
})();
new CopyAsGFM();
/* eslint-disable func-names, space-before-function-paren, one-var, no-var, one-var-declaration-per-line, prefer-template, quotes, no-unused-vars, prefer-arrow-callback, max-len */
/* global Clipboard */
window.Clipboard = require('vendor/clipboard');
import Clipboard from 'vendor/clipboard';
(function() {
var genericError, genericSuccess, showTooltip;
var genericError, genericSuccess, showTooltip;
genericSuccess = function(e) {
genericSuccess = function(e) {
showTooltip(e.trigger, 'Copied');
// Clear the selection and blur the trigger so it loses its border
e.clearSelection();
return $(e.trigger).blur();
};
};
// Safari doesn't support `execCommand`, so instead we inform the user to
// copy manually.
//
// See http://clipboardjs.com/#browser-support
genericError = function(e) {
// Safari doesn't support `execCommand`, so instead we inform the user to
// copy manually.
//
// See http://clipboardjs.com/#browser-support
genericError = function(e) {
var key;
if (/Mac/i.test(navigator.userAgent)) {
key = '&#8984;'; // Command
......@@ -25,9 +23,9 @@ window.Clipboard = require('vendor/clipboard');
key = 'Ctrl';
}
return showTooltip(e.trigger, "Press " + key + "-C to copy");
};
};
showTooltip = function(target, title) {
showTooltip = function(target, title) {
var $target = $(target);
var originalTitle = $target.data('original-title');
......@@ -37,13 +35,12 @@ window.Clipboard = require('vendor/clipboard');
.tooltip('show')
.attr('title', originalTitle)
.tooltip('fixTitle');
};
};
$(function() {
$(function() {
var clipboard;
clipboard = new Clipboard('[data-clipboard-target], [data-clipboard-text]');
clipboard.on('success', genericSuccess);
return clipboard.on('error', genericError);
});
}).call(window);
});
/* 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 */
(function (w) {
class CreateLabelDropdown {
class CreateLabelDropdown {
constructor ($el, namespacePath, projectPath) {
this.$el = $el;
this.namespacePath = namespacePath;
......@@ -122,11 +121,7 @@
}
});
}
}
if (!w.gl) {
w.gl = {};
}
}
gl.CreateLabelDropdown = CreateLabelDropdown;
})(window);
window.gl = window.gl || {};
gl.CreateLabelDropdown = CreateLabelDropdown;
......@@ -2,11 +2,10 @@
require('./lib/utils/url_utility');
(() => {
const UNFOLD_COUNT = 20;
let isBound = false;
const UNFOLD_COUNT = 20;
let isBound = false;
class Diff {
class Diff {
constructor() {
const $diffFile = $('.files .diff-file');
$diffFile.singleFileDiff();
......@@ -123,8 +122,7 @@ require('./lib/utils/url_utility');
.addClass('hll');
}
}
}
}
window.gl = window.gl || {};
window.gl.Diff = Diff;
})();
window.gl = window.gl || {};
window.gl.Diff = Diff;
/* global Vue */
/* global CommentsStore */
(() => {
const NewIssueForDiscussion = Vue.extend({
props: {
discussionId: {
type: String,
required: true,
},
},
data() {
return {
discussions: CommentsStore.state,
};
},
computed: {
discussion() {
return this.discussions[this.discussionId];
},
showButton() {
if (this.discussion) return !this.discussion.isResolved();
return false;
},
},
});
Vue.component('new-issue-for-discussion-btn', NewIssueForDiscussion);
})();
......@@ -14,10 +14,11 @@ require('./components/resolve_btn');
require('./components/resolve_count');
require('./components/resolve_discussion_btn');
require('./components/diff_note_avatars');
require('./components/new_issue_for_discussion');
$(() => {
const projectPath = document.querySelector('.merge-request').dataset.projectPath;
const COMPONENT_SELECTOR = 'resolve-btn, resolve-discussion-btn, jump-to-discussion, comment-and-resolve-btn';
const COMPONENT_SELECTOR = 'resolve-btn, resolve-discussion-btn, jump-to-discussion, comment-and-resolve-btn, new-issue-for-discussion-btn';
window.gl = window.gl || {};
window.gl.diffNoteApps = {};
......
......@@ -37,9 +37,11 @@ import PrometheusGraph from './monitoring/prometheus_graph'; // TODO: Maybe Make
import Issue from './issue';
import BindInOut from './behaviors/bind_in_out';
import GroupName from './group_name';
import GroupsList from './groups_list';
import ProjectsList from './projects_list';
import MiniPipelineGraph from './mini_pipeline_graph_dropdown';
import BlobLinePermalinkUpdater from './blob/blob_line_permalink_updater';
const ShortcutsBlob = require('./shortcuts_blob');
const UserCallout = require('./user_callout');
......@@ -66,6 +68,25 @@ const UserCallout = require('./user_callout');
}
path = page.split(':');
shortcut_handler = null;
function initBlob() {
new LineHighlighter();
new BlobLinePermalinkUpdater(
document.querySelector('#blob-content-holder'),
'.diff-line-num[data-line-number]',
document.querySelectorAll('.js-data-file-blob-permalink-url, .js-blob-blame-link'),
);
shortcut_handler = new ShortcutsNavigation();
fileBlobPermalinkUrlElement = document.querySelector('.js-data-file-blob-permalink-url');
fileBlobPermalinkUrl = fileBlobPermalinkUrlElement && fileBlobPermalinkUrlElement.getAttribute('href');
new ShortcutsBlob({
skipResetBindings: true,
fileBlobPermalinkUrl,
});
}
switch (page) {
case 'sessions:new':
new UsernameValidator();
......@@ -180,10 +201,13 @@ const UserCallout = require('./user_callout');
new gl.Diff();
new ZenMode();
shortcut_handler = new ShortcutsNavigation();
new MiniPipelineGraph({
container: '.js-commit-pipeline-graph',
}).bindEvents();
break;
case 'projects:commit:pipelines':
new MiniPipelineGraph({
container: '.js-pipeline-table',
container: '.js-commit-pipeline-graph',
}).bindEvents();
break;
case 'projects:commits:show':
......@@ -258,27 +282,13 @@ const UserCallout = require('./user_callout');
break;
case 'projects:blob:show':
gl.TargetBranchDropDown.bootstrap();
new LineHighlighter();
shortcut_handler = new ShortcutsNavigation();
fileBlobPermalinkUrlElement = document.querySelector('.js-data-file-blob-permalink-url');
fileBlobPermalinkUrl = fileBlobPermalinkUrlElement && fileBlobPermalinkUrlElement.getAttribute('href');
new ShortcutsBlob({
skipResetBindings: true,
fileBlobPermalinkUrl,
});
initBlob();
break;
case 'projects:blob:edit':
gl.TargetBranchDropDown.bootstrap();
break;
case 'projects:blame:show':
new LineHighlighter();
shortcut_handler = new ShortcutsNavigation();
fileBlobPermalinkUrlElement = document.querySelector('.js-data-file-blob-permalink-url');
fileBlobPermalinkUrl = fileBlobPermalinkUrlElement && fileBlobPermalinkUrlElement.getAttribute('href');
new ShortcutsBlob({
skipResetBindings: true,
fileBlobPermalinkUrl,
});
initBlob();
break;
case 'groups:labels:new':
case 'groups:labels:edit':
......@@ -362,6 +372,9 @@ const UserCallout = require('./user_callout');
shortcut_handler = new ShortcutsDashboardNavigation();
new UserCallout();
break;
case 'groups':
new GroupName();
break;
case 'profiles':
new NotificationsForm();
new NotificationsDropdown();
......@@ -369,6 +382,7 @@ const UserCallout = require('./user_callout');
case 'projects':
new Project();
new ProjectAvatar();
new GroupName();
switch (path[1]) {
case 'compare':
new CompareAutocomplete();
......
......@@ -74,6 +74,9 @@ require('../window')(function(w){
this._loadUrlData(config.endpoint)
.then(function(d) {
self._loadData(d, config, self);
}, function(xhrError) {
// TODO: properly handle errors due to XHR cancellation
return;
}).catch(function(e) {
throw new droplabAjaxException(e.message || e);
});
......
......@@ -82,6 +82,9 @@ require('../window')(function(w){
this._loadUrlData(url)
.then(function(data) {
self._loadData(data, config, self);
}, function(xhrError) {
// TODO: properly handle errors due to XHR cancellation
return;
});
}
},
......
......@@ -3,8 +3,7 @@
require('./preview_markdown');
(function() {
this.DropzoneInput = (function() {
window.DropzoneInput = (function() {
function DropzoneInput(form) {
var $mdArea, alertAttr, alertClass, appendToTextArea, btnAlert, child, closeAlertMessage, closeSpinner, divAlert, divHover, divSpinner, dropzone, form_dropzone, form_textarea, getFilename, handlePaste, iconPaperclip, iconSpinner, insertToTextArea, isImage, max_file_size, pasteText, project_uploads_path, showError, showSpinner, uploadFile, uploadProgress;
Dropzone.autoDiscover = false;
......@@ -216,5 +215,4 @@ require('./preview_markdown');
}
return DropzoneInput;
})();
}).call(window);
})();
......@@ -2,8 +2,7 @@
/* global dateFormat */
/* global Pikaday */
(function(global) {
class DueDateSelect {
class DueDateSelect {
constructor({ $dropdown, $loading } = {}) {
const $dropdownParent = $dropdown.closest('.dropdown');
const $block = $dropdown.closest('.block');
......@@ -156,9 +155,9 @@
return this.$loading.fadeOut();
});
}
}
}
class DueDateSelectors {
class DueDateSelectors {
constructor() {
this.initMilestoneDatePicker();
this.initIssuableSelect();
......@@ -198,7 +197,7 @@
});
});
}
}
}
global.DueDateSelectors = DueDateSelectors;
})(window.gl || (window.gl = {}));
window.gl = window.gl || {};
window.gl.DueDateSelectors = DueDateSelectors;
/* eslint-disable no-param-reassign, no-new */
/* global Flash */
import EnvironmentsService from '../services/environments_service';
import EnvironmentTable from './environments_table';
import EnvironmentsStore from '../stores/environments_store';
import eventHub from '../event_hub';
const Vue = window.Vue = require('vue');
window.Vue.use(require('vue-resource'));
const EnvironmentsService = require('../services/environments_service');
const EnvironmentTable = require('./environments_table');
const EnvironmentsStore = require('../stores/environments_store');
require('../../vue_shared/components/table_pagination');
require('../../lib/utils/common_utils');
require('../../vue_shared/vue_resource_interceptor');
module.exports = Vue.component('environment-component', {
export default Vue.component('environment-component', {
components: {
'environment-table': EnvironmentTable,
......@@ -66,33 +67,15 @@ module.exports = Vue.component('environment-component', {
* Toggles loading property.
*/
created() {
const scope = gl.utils.getParameterByName('scope') || this.visibility;
const pageNumber = gl.utils.getParameterByName('page') || this.pageNumber;
const endpoint = `${this.endpoint}?scope=${scope}&page=${pageNumber}`;
this.service = new EnvironmentsService(this.endpoint);
const service = new EnvironmentsService(endpoint);
this.fetchEnvironments();
this.isLoading = true;
eventHub.$on('refreshEnvironments', this.fetchEnvironments);
},
return service.get()
.then(resp => ({
headers: resp.headers,
body: resp.json(),
}))
.then((response) => {
this.store.storeAvailableCount(response.body.available_count);
this.store.storeStoppedCount(response.body.stopped_count);
this.store.storeEnvironments(response.body.environments);
this.store.setPagination(response.headers);
})
.then(() => {
this.isLoading = false;
})
.catch(() => {
this.isLoading = false;
new Flash('An error occurred while fetching the environments.', 'alert');
});
beforeDestroyed() {
eventHub.$off('refreshEnvironments');
},
methods: {
......@@ -112,6 +95,32 @@ module.exports = Vue.component('environment-component', {
gl.utils.visitUrl(param);
return param;
},
fetchEnvironments() {
const scope = gl.utils.getParameterByName('scope') || this.visibility;
const pageNumber = gl.utils.getParameterByName('page') || this.pageNumber;
this.isLoading = true;
return this.service.get(scope, pageNumber)
.then(resp => ({
headers: resp.headers,
body: resp.json(),
}))
.then((response) => {
this.store.storeAvailableCount(response.body.available_count);
this.store.storeStoppedCount(response.body.stopped_count);
this.store.storeEnvironments(response.body.environments);
this.store.setPagination(response.headers);
})
.then(() => {
this.isLoading = false;
})
.catch(() => {
this.isLoading = false;
new Flash('An error occurred while fetching the environments.');
});
},
},
template: `
......@@ -144,7 +153,7 @@ module.exports = Vue.component('environment-component', {
<div class="content-list environments-container">
<div class="environments-list-loading text-center" v-if="isLoading">
<i class="fa fa-spinner fa-spin"></i>
<i class="fa fa-spinner fa-spin" aria-hidden="true"></i>
</div>
<div class="blank-state blank-state-no-icon"
......@@ -173,7 +182,8 @@ module.exports = Vue.component('environment-component', {
<environment-table
:environments="state.environments"
:can-create-deployment="canCreateDeploymentParsed"
:can-read-environment="canReadEnvironmentParsed"/>
:can-read-environment="canReadEnvironmentParsed"
:service="service"/>
</div>
<table-pagination v-if="state.paginationInformation && state.paginationInformation.totalPages > 1"
......
const Vue = require('vue');
const playIconSvg = require('icons/_icon_play.svg');
/* global Flash */
/* eslint-disable no-new */
module.exports = Vue.component('actions-component', {
import playIconSvg from 'icons/_icon_play.svg';
import eventHub from '../event_hub';
export default {
props: {
actions: {
type: Array,
required: false,
default: () => [],
},
service: {
type: Object,
required: true,
},
},
data() {
return { playIconSvg };
return {
playIconSvg,
isLoading: false,
};
},
methods: {
onClickAction(endpoint) {
this.isLoading = true;
this.service.postAction(endpoint)
.then(() => {
this.isLoading = false;
eventHub.$emit('refreshEnvironments');
})
.catch(() => {
this.isLoading = false;
new Flash('An error occured while making the request.');
});
},
},
template: `
<div class="btn-group" role="group">
<button class="dropdown btn btn-default dropdown-new" data-toggle="dropdown">
<button
class="dropdown btn btn-default dropdown-new js-dropdown-play-icon-container"
data-toggle="dropdown"
:disabled="isLoading">
<span>
<span class="js-dropdown-play-icon-container" v-html="playIconSvg"></span>
<i class="fa fa-caret-down"></i>
<span v-html="playIconSvg"></span>
<i class="fa fa-caret-down" aria-hidden="true"></i>
<i v-if="isLoading" class="fa fa-spinner fa-spin" aria-hidden="true"></i>
</span>
<ul class="dropdown-menu dropdown-menu-align-right">
<li v-for="action in actions">
<a :href="action.play_path"
data-method="post"
rel="nofollow"
class="js-manual-action-link">
<button
@click="onClickAction(action.play_path)"
class="js-manual-action-link no-btn">
${playIconSvg}
<span>
{{action.name}}
</span>
</a>
</button>
</li>
</ul>
</button>
</div>
`,
});
};
/**
* Renders the external url link in environments table.
*/
const Vue = require('vue');
module.exports = Vue.component('external-url-component', {
export default {
props: {
externalUrl: {
type: String,
......@@ -12,8 +10,12 @@ module.exports = Vue.component('external-url-component', {
},
template: `
<a class="btn external_url" :href="externalUrl" target="_blank">
<i class="fa fa-external-link"></i>
<a
class="btn external_url"
:href="externalUrl"
target="_blank"
title="Environment external URL">
<i class="fa fa-external-link" aria-hidden="true"></i>
</a>
`,
});
};
const Vue = require('vue');
const Timeago = require('timeago.js');
require('../../lib/utils/text_utility');
require('../../vue_shared/components/commit');
const ActionsComponent = require('./environment_actions');
const ExternalUrlComponent = require('./environment_external_url');
const StopComponent = require('./environment_stop');
const RollbackComponent = require('./environment_rollback');
const TerminalButtonComponent = require('./environment_terminal_button');
import Timeago from 'timeago.js';
import ActionsComponent from './environment_actions';
import ExternalUrlComponent from './environment_external_url';
import StopComponent from './environment_stop';
import RollbackComponent from './environment_rollback';
import TerminalButtonComponent from './environment_terminal_button';
import '../../lib/utils/text_utility';
import '../../vue_shared/components/commit';
/**
* Envrionment Item Component
......@@ -17,7 +15,7 @@ const TerminalButtonComponent = require('./environment_terminal_button');
const timeagoInstance = new Timeago();
module.exports = Vue.component('environment-item', {
export default {
components: {
'commit-component': gl.CommitComponent,
......@@ -46,6 +44,11 @@ module.exports = Vue.component('environment-item', {
required: false,
default: false,
},
service: {
type: Object,
required: true,
},
},
computed: {
......@@ -489,22 +492,25 @@ module.exports = Vue.component('environment-item', {
<td class="environments-actions">
<div v-if="!model.isFolder" class="btn-group pull-right" role="group">
<actions-component v-if="hasManualActions && canCreateDeployment"
:service="service"
:actions="manualActions"/>
<external-url-component v-if="externalURL && canReadEnvironment"
:external-url="externalURL"/>
<stop-component v-if="hasStopAction && canCreateDeployment"
:stop-url="model.stop_path"/>
:stop-url="model.stop_path"
:service="service"/>
<terminal-button-component v-if="model && model.terminal_path"
:terminal-path="model.terminal_path"/>
<rollback-component v-if="canRetry && canCreateDeployment"
:is-last-deployment="isLastDeployment"
:retry-url="retryUrl"/>
:retry-url="retryUrl"
:service="service"/>
</div>
</td>
</tr>
`,
});
};
/* global Flash */
/* eslint-disable no-new */
/**
* Renders Rollback or Re deploy button in environments table depending
* of the provided property `isLastDeployment`
* of the provided property `isLastDeployment`.
*
* Makes a post request when the button is clicked.
*/
const Vue = require('vue');
import eventHub from '../event_hub';
module.exports = Vue.component('rollback-component', {
export default {
props: {
retryUrl: {
type: String,
......@@ -15,16 +19,49 @@ module.exports = Vue.component('rollback-component', {
type: Boolean,
default: true,
},
service: {
type: Object,
required: true,
},
},
data() {
return {
isLoading: false,
};
},
methods: {
onClick() {
this.isLoading = true;
this.service.postAction(this.retryUrl)
.then(() => {
this.isLoading = false;
eventHub.$emit('refreshEnvironments');
})
.catch(() => {
this.isLoading = false;
new Flash('An error occured while making the request.');
});
},
},
template: `
<a class="btn" :href="retryUrl" data-method="post" rel="nofollow">
<button type="button"
class="btn"
@click="onClick"
:disabled="isLoading">
<span v-if="isLastDeployment">
Re-deploy
</span>
<span v-else>
Rollback
</span>
</a>
<i v-if="isLoading" class="fa fa-spinner fa-spin" aria-hidden="true"></i>
</button>
`,
});
};
/* global Flash */
/* eslint-disable no-new, no-alert */
/**
* Renders the stop "button" that allows stop an environment.
* Used in environments table.
*/
const Vue = require('vue');
import eventHub from '../event_hub';
module.exports = Vue.component('stop-component', {
export default {
props: {
stopUrl: {
type: String,
default: '',
},
service: {
type: Object,
required: true,
},
},
data() {
return {
isLoading: false,
};
},
methods: {
onClick() {
if (confirm('Are you sure you want to stop this environment?')) {
this.isLoading = true;
this.service.postAction(this.retryUrl)
.then(() => {
this.isLoading = false;
eventHub.$emit('refreshEnvironments');
})
.catch(() => {
this.isLoading = false;
new Flash('An error occured while making the request.', 'alert');
});
}
},
},
template: `
<a class="btn stop-env-link"
:href="stopUrl"
data-confirm="Are you sure you want to stop this environment?"
data-method="post"
rel="nofollow">
<button type="button"
class="btn stop-env-link"
@click="onClick"
:disabled="isLoading"
title="Stop Environment">
<i class="fa fa-stop stop-env-icon" aria-hidden="true"></i>
</a>
<i v-if="isLoading" class="fa fa-spinner fa-spin" aria-hidden="true"></i>
</button>
`,
});
};
......@@ -2,13 +2,13 @@
* Renders a terminal button to open a web terminal.
* Used in environments table.
*/
const Vue = require('vue');
const terminalIconSvg = require('icons/_icon_terminal.svg');
import terminalIconSvg from 'icons/_icon_terminal.svg';
module.exports = Vue.component('terminal-button-component', {
export default {
props: {
terminalPath: {
type: String,
required: false,
default: '',
},
},
......@@ -19,8 +19,9 @@ module.exports = Vue.component('terminal-button-component', {
template: `
<a class="btn terminal-button"
title="Open web terminal"
:href="terminalPath">
${terminalIconSvg}
</a>
`,
});
};
/**
* Render environments table.
*/
const Vue = require('vue');
const EnvironmentItem = require('./environment_item');
module.exports = Vue.component('environment-table-component', {
import EnvironmentItem from './environment_item';
export default {
components: {
'environment-item': EnvironmentItem,
},
......@@ -28,6 +26,11 @@ module.exports = Vue.component('environment-table-component', {
required: false,
default: false,
},
service: {
type: Object,
required: true,
},
},
template: `
......@@ -48,9 +51,10 @@ module.exports = Vue.component('environment-table-component', {
<tr is="environment-item"
:model="model"
:can-create-deployment="canCreateDeployment"
:can-read-environment="canReadEnvironment"></tr>
:can-read-environment="canReadEnvironment"
:service="service"></tr>
</template>
</tbody>
</table>
`,
});
};
const EnvironmentsComponent = require('./components/environment');
import EnvironmentsComponent from './components/environment';
$(() => {
window.gl = window.gl || {};
......
import Vue from 'vue';
export default new Vue();
const EnvironmentsFolderComponent = require('./environments_folder_view');
import EnvironmentsFolderComponent from './environments_folder_view';
$(() => {
window.gl = window.gl || {};
......
/* eslint-disable no-param-reassign, no-new */
/* global Flash */
import EnvironmentsService from '../services/environments_service';
import EnvironmentTable from '../components/environments_table';
import EnvironmentsStore from '../stores/environments_store';
const Vue = window.Vue = require('vue');
window.Vue.use(require('vue-resource'));
const EnvironmentsService = require('../services/environments_service');
const EnvironmentTable = require('../components/environments_table');
const EnvironmentsStore = require('../stores/environments_store');
require('../../vue_shared/components/table_pagination');
require('../../lib/utils/common_utils');
require('../../vue_shared/vue_resource_interceptor');
module.exports = Vue.component('environment-folder-view', {
export default Vue.component('environment-folder-view', {
components: {
'environment-table': EnvironmentTable,
......@@ -88,11 +88,11 @@ module.exports = Vue.component('environment-folder-view', {
const endpoint = `${this.endpoint}?scope=${scope}&page=${pageNumber}`;
const service = new EnvironmentsService(endpoint);
this.service = new EnvironmentsService(endpoint);
this.isLoading = true;
return service.get()
return this.service.get()
.then(resp => ({
headers: resp.headers,
body: resp.json(),
......@@ -168,13 +168,12 @@ module.exports = Vue.component('environment-folder-view', {
:can-read-environment="canReadEnvironmentParsed"
:play-icon-svg="playIconSvg"
:terminal-icon-svg="terminalIconSvg"
:commit-icon-svg="commitIconSvg">
</environment-table>
:commit-icon-svg="commitIconSvg"
:service="service"/>
<table-pagination v-if="state.paginationInformation && state.paginationInformation.totalPages > 1"
:change="changePage"
:pageInfo="state.paginationInformation">
</table-pagination>
:pageInfo="state.paginationInformation"/>
</div>
</div>
</div>
......
const Vue = require('vue');
/* eslint-disable class-methods-use-this */
import Vue from 'vue';
class EnvironmentsService {
export default class EnvironmentsService {
constructor(endpoint) {
this.environments = Vue.resource(endpoint);
}
get() {
return this.environments.get();
get(scope, page) {
return this.environments.get({ scope, page });
}
}
module.exports = EnvironmentsService;
postAction(endpoint) {
return Vue.http.post(endpoint, {}, { emulateJSON: true });
}
}
require('~/lib/utils/common_utils');
import '~/lib/utils/common_utils';
/**
* Environments Store.
*
* Stores received environments, count of stopped environments and count of
* available environments.
*/
class EnvironmentsStore {
export default class EnvironmentsStore {
constructor() {
this.state = {};
this.state.environments = [];
......@@ -86,5 +87,3 @@ class EnvironmentsStore {
return count;
}
}
module.exports = EnvironmentsStore;
/* eslint-disable no-extend-native, func-names, space-before-function-paren, space-infix-ops, strict, max-len */
// TODO: remove this
'use strict';
Array.prototype.first = function() {
// eslint-disable-next-line no-extend-native
Array.prototype.first = function first() {
return this[0];
};
Array.prototype.last = function() {
return this[this.length-1];
};
Array.prototype.find = Array.prototype.find || function(predicate, ...args) {
if (!this) throw new TypeError('Array.prototype.find called on null or undefined');
if (typeof predicate !== 'function') throw new TypeError('predicate must be a function');
const list = Object(this);
const thisArg = args[1];
let value = {};
for (let i = 0; i < list.length; i += 1) {
value = list[i];
if (predicate.call(thisArg, value, i, list)) return value;
}
return undefined;
// eslint-disable-next-line no-extend-native
Array.prototype.last = function last() {
return this[this.length - 1];
};
/* global CustomEvent */
/* eslint-disable no-global-assign */
// Custom event support for IE
CustomEvent = function CustomEvent(event, parameters) {
const params = parameters || { bubbles: false, cancelable: false, detail: undefined };
const evt = document.createEvent('CustomEvent');
evt.initCustomEvent(event, params.bubbles, params.cancelable, params.detail);
return evt;
};
CustomEvent.prototype = window.Event.prototype;
/* eslint-disable func-names, space-before-function-paren, object-shorthand, comma-dangle, max-len */
// Disable an element and add the 'disabled' Bootstrap class
(function() {
$.fn.extend({
disable: function() {
return $(this).attr('disabled', 'disabled').addClass('disabled');
}
});
// Enable an element and remove the 'disabled' Bootstrap class
$.fn.extend({
enable: function() {
return $(this).removeAttr('disabled').removeClass('disabled');
}
});
}).call(window);
/* eslint-disable no-restricted-syntax */
// Adapted from https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_Objects/Object/assign#Polyfill
if (typeof Object.assign !== 'function') {
Object.assign = function assign(target, ...args) {
if (target == null) { // TypeError if undefined or null
throw new TypeError('Cannot convert undefined or null to object');
}
const to = Object(target);
for (let index = 0; index < args.length; index += 1) {
const nextSource = args[index];
if (nextSource != null) { // Skip over if undefined or null
for (const nextKey in nextSource) {
// Avoid bugs when hasOwnProperty is shadowed
if (Object.prototype.hasOwnProperty.call(nextSource, nextKey)) {
to[nextKey] = nextSource[nextKey];
}
}
}
}
return to;
};
}
import 'string.prototype.codepointat';
import 'string.fromcodepoint';
......@@ -2,11 +2,10 @@
/* global FilesCommentButton */
/* global notes */
(function() {
let $commentButtonTemplate;
var bind = function(fn, me) { return function() { return fn.apply(me, arguments); }; };
let $commentButtonTemplate;
var bind = function(fn, me) { return function() { return fn.apply(me, arguments); }; };
this.FilesCommentButton = (function() {
window.FilesCommentButton = (function() {
var COMMENT_BUTTON_CLASS, EMPTY_CELL_CLASS, LINE_COLUMN_CLASSES, LINE_CONTENT_CLASS, LINE_HOLDER_CLASS, LINE_NUMBER_CLASS, OLD_LINE_CLASS, TEXT_FILE_SELECTOR, UNFOLDABLE_LINE_CLASS;
COMMENT_BUTTON_CLASS = '.add-diff-note';
......@@ -126,9 +125,9 @@
};
return FilesCommentButton;
})();
})();
$.fn.filesCommentButton = function() {
$.fn.filesCommentButton = function() {
$commentButtonTemplate = $('<button name="button" type="submit" class="add-diff-note js-add-diff-note-button" title="Add a comment to this line"><i class="fa fa-comment-o"></i></button>');
if (!(this && (this.parent().data('can-create-note') != null))) {
......@@ -139,5 +138,4 @@
return $.data(this, 'filesCommentButton', new FilesCommentButton($(this)));
}
});
};
}).call(window);
};
......@@ -2,6 +2,7 @@
* Makes search request for content when user types a value in the search input.
* Updates the html content of the page with the received one.
*/
export default class FilterableList {
constructor(form, filter, holder) {
this.filterForm = form;
......
/* eslint-disable class-methods-use-this */
let container = document;
class FilteredSearchContainerClass {
set container(containerParam) {
container = containerParam;
}
get container() {
return container;
}
}
export default new FilteredSearchContainerClass();
......@@ -45,7 +45,7 @@ require('./filtered_search_dropdown');
gl.FilteredSearchVisualTokens.addSearchVisualToken(searchTerms.join(' '));
}
gl.FilteredSearchDropdownManager.addWordToInput(token.replace(':', ''));
gl.FilteredSearchDropdownManager.addWordToInput(token.replace(':', ''), '', false, this.container);
}
this.dismissDropdown();
this.dispatchInputEvent();
......@@ -57,13 +57,15 @@ require('./filtered_search_dropdown');
const dropdownData = [];
[].forEach.call(this.input.closest('.filtered-search-input-container').querySelectorAll('.dropdown-menu'), (dropdownMenu) => {
const { icon, hint, tag } = dropdownMenu.dataset;
const { icon, hint, tag, type } = dropdownMenu.dataset;
if (icon && hint && tag) {
dropdownData.push({
dropdownData.push(
Object.assign({
icon: `fa-${icon}`,
hint,
tag: `&lt;${tag}&gt;`,
});
}, type && { type }),
);
}
});
......
import FilteredSearchContainer from './container';
(() => {
class DropdownUtils {
static getEscapedText(text) {
......@@ -51,14 +53,18 @@
static filterHint(input, item) {
const updatedItem = item;
const searchInput = gl.DropdownUtils.getSearchInput(input);
let { lastToken } = gl.FilteredSearchTokenizer.processTokens(searchInput);
lastToken = lastToken.key || lastToken || '';
if (!lastToken || searchInput.split('').last() === ' ') {
const searchInput = gl.DropdownUtils.getSearchQuery(input);
const { lastToken, tokens } = gl.FilteredSearchTokenizer.processTokens(searchInput);
const lastKey = lastToken.key || lastToken || '';
const allowMultiple = item.type === 'array';
const itemInExistingTokens = tokens.some(t => t.key === item.hint);
if (!allowMultiple && itemInExistingTokens) {
updatedItem.droplab_hidden = true;
} else if (!lastKey || searchInput.split('').last() === ' ') {
updatedItem.droplab_hidden = false;
} else if (lastToken) {
const split = lastToken.split(':');
} else if (lastKey) {
const split = lastKey.split(':');
const tokenName = split[0].split(' ').last();
const match = updatedItem.hint.indexOf(tokenName.toLowerCase()) === -1;
......@@ -81,7 +87,8 @@
// Determines the full search query (visual tokens + input)
static getSearchQuery(untilInput = false) {
const tokens = [].slice.call(document.querySelectorAll('.tokens-container li'));
const container = FilteredSearchContainer.container;
const tokens = [].slice.call(container.querySelectorAll('.tokens-container li'));
const values = [];
if (untilInput) {
......@@ -110,7 +117,7 @@
const { isLastVisualTokenValid } =
gl.FilteredSearchVisualTokens.getLastVisualTokenBeforeInput();
const input = document.querySelector('.filtered-search');
const input = FilteredSearchContainer.container.querySelector('.filtered-search');
const inputValue = input && input.value;
if (isLastVisualTokenValid) {
......
/* global DropLab */
import FilteredSearchContainer from './container';
(() => {
class FilteredSearchDropdownManager {
constructor(baseEndpoint = '', page) {
this.container = FilteredSearchContainer.container;
this.baseEndpoint = baseEndpoint.replace(/\/$/, '');
this.tokenizer = gl.FilteredSearchTokenizer;
this.filteredSearchTokenKeys = gl.FilteredSearchTokenKeys;
this.filteredSearchInput = document.querySelector('.filtered-search');
this.filteredSearchInput = this.container.querySelector('.filtered-search');
this.page = page;
this.setupMapping();
......@@ -31,35 +33,35 @@
author: {
reference: null,
gl: 'DropdownUser',
element: document.querySelector('#js-dropdown-author'),
element: this.container.querySelector('#js-dropdown-author'),
},
assignee: {
reference: null,
gl: 'DropdownUser',
element: document.querySelector('#js-dropdown-assignee'),
element: this.container.querySelector('#js-dropdown-assignee'),
},
milestone: {
reference: null,
gl: 'DropdownNonUser',
extraArguments: [`${this.baseEndpoint}/milestones.json`, '%'],
element: document.querySelector('#js-dropdown-milestone'),
element: this.container.querySelector('#js-dropdown-milestone'),
},
label: {
reference: null,
gl: 'DropdownNonUser',
extraArguments: [`${this.baseEndpoint}/labels.json`, '~'],
element: document.querySelector('#js-dropdown-label'),
element: this.container.querySelector('#js-dropdown-label'),
},
hint: {
reference: null,
gl: 'DropdownHint',
element: document.querySelector('#js-dropdown-hint'),
element: this.container.querySelector('#js-dropdown-hint'),
},
};
}
static addWordToInput(tokenName, tokenValue = '', clicked = false) {
const input = document.querySelector('.filtered-search');
const input = FilteredSearchContainer.container.querySelector('.filtered-search');
gl.FilteredSearchVisualTokens.addFilterVisualToken(tokenName, tokenValue);
input.value = '';
......@@ -75,13 +77,13 @@
updateDropdownOffset(key) {
// Always align dropdown with the input field
let offset = this.filteredSearchInput.getBoundingClientRect().left - document.querySelector('.scroll-container').getBoundingClientRect().left;
let offset = this.filteredSearchInput.getBoundingClientRect().left - this.container.querySelector('.scroll-container').getBoundingClientRect().left;
const maxInputWidth = 240;
const currentDropdownWidth = this.mapping[key].element.clientWidth || maxInputWidth;
// Make sure offset never exceeds the input container
const offsetMaxWidth = document.querySelector('.scroll-container').clientWidth - currentDropdownWidth;
const offsetMaxWidth = this.container.querySelector('.scroll-container').clientWidth - currentDropdownWidth;
if (offsetMaxWidth < offset) {
offset = offsetMaxWidth;
}
......@@ -162,6 +164,10 @@
}
resetDropdowns() {
if (!this.currentDropdown) {
return;
}
// Force current dropdown to hide
this.mapping[this.currentDropdown].reference.hideDropdown();
......
import FilteredSearchContainer from './container';
(() => {
class FilteredSearchManager {
constructor(page) {
this.filteredSearchInput = document.querySelector('.filtered-search');
this.clearSearchButton = document.querySelector('.clear-search');
this.tokensContainer = document.querySelector('.tokens-container');
this.container = FilteredSearchContainer.container;
this.filteredSearchInput = this.container.querySelector('.filtered-search');
this.clearSearchButton = this.container.querySelector('.clear-search');
this.tokensContainer = this.container.querySelector('.tokens-container');
this.filteredSearchTokenKeys = gl.FilteredSearchTokenKeys;
if (this.filteredSearchInput) {
......@@ -38,7 +41,8 @@
this.editTokenWrapper = this.editToken.bind(this);
this.tokenChange = this.tokenChange.bind(this);
this.filteredSearchInput.form.addEventListener('submit', this.handleFormSubmit);
this.filteredSearchInputForm = this.filteredSearchInput.form;
this.filteredSearchInputForm.addEventListener('submit', this.handleFormSubmit);
this.filteredSearchInput.addEventListener('input', this.setDropdownWrapper);
this.filteredSearchInput.addEventListener('input', this.toggleClearSearchButtonWrapper);
this.filteredSearchInput.addEventListener('input', this.handleInputPlaceholderWrapper);
......@@ -56,7 +60,7 @@
}
unbindEvents() {
this.filteredSearchInput.form.removeEventListener('submit', this.handleFormSubmit);
this.filteredSearchInputForm.removeEventListener('submit', this.handleFormSubmit);
this.filteredSearchInput.removeEventListener('input', this.setDropdownWrapper);
this.filteredSearchInput.removeEventListener('input', this.toggleClearSearchButtonWrapper);
this.filteredSearchInput.removeEventListener('input', this.handleInputPlaceholderWrapper);
......@@ -105,8 +109,15 @@
e.preventDefault();
if (!activeElements.length) {
if (this.isHandledAsync) {
e.stopImmediatePropagation();
this.filteredSearchInput.blur();
this.dropdownManager.resetDropdowns();
} else {
// Prevent droplab from opening dropdown
this.dropdownManager.destroyDroplab();
}
this.search();
}
......@@ -124,7 +135,7 @@
}
unselectEditTokens(e) {
const inputContainer = document.querySelector('.filtered-search-input-container');
const inputContainer = this.container.querySelector('.filtered-search-input-container');
const isElementInFilteredSearch = inputContainer && inputContainer.contains(e.target);
const isElementInFilterDropdown = e.target.closest('.filter-dropdown') !== null;
const isElementTokensContainer = e.target.classList.contains('tokens-container');
......@@ -199,6 +210,10 @@
this.handleInputPlaceholder();
this.dropdownManager.resetDropdowns();
if (this.isHandledAsync) {
this.search();
}
}
handleInputVisualToken() {
......@@ -345,8 +360,12 @@
const parameterizedUrl = `?scope=all&utf8=✓&${paths.join('&')}`;
if (this.updateObject) {
this.updateObject(parameterizedUrl);
} else {
gl.utils.visitUrl(parameterizedUrl);
}
}
getUsernameParams() {
const usernamesById = {};
......
......@@ -42,6 +42,10 @@
url: 'milestone_title=%23upcoming',
tokenKey: 'milestone',
value: 'upcoming',
}, {
url: 'milestone_title=%23started',
tokenKey: 'milestone',
value: 'started',
}, {
url: 'label_name[]=No+Label',
tokenKey: 'label',
......
import FilteredSearchContainer from './container';
class FilteredSearchVisualTokens {
static getLastVisualTokenBeforeInput() {
const inputLi = document.querySelector('.input-token');
const inputLi = FilteredSearchContainer.container.querySelector('.input-token');
const lastVisualToken = inputLi && inputLi.previousElementSibling;
return {
......@@ -10,7 +12,7 @@ class FilteredSearchVisualTokens {
}
static unselectTokens() {
const otherTokens = document.querySelectorAll('.js-visual-token .selectable.selected');
const otherTokens = FilteredSearchContainer.container.querySelectorAll('.js-visual-token .selectable.selected');
[].forEach.call(otherTokens, t => t.classList.remove('selected'));
}
......@@ -24,7 +26,7 @@ class FilteredSearchVisualTokens {
}
static removeSelectedToken() {
const selected = document.querySelector('.js-visual-token .selected');
const selected = FilteredSearchContainer.container.querySelector('.js-visual-token .selected');
if (selected) {
const li = selected.closest('.js-visual-token');
......@@ -54,8 +56,8 @@ class FilteredSearchVisualTokens {
}
li.querySelector('.name').innerText = name;
const tokensContainer = document.querySelector('.tokens-container');
const input = document.querySelector('.filtered-search');
const tokensContainer = FilteredSearchContainer.container.querySelector('.tokens-container');
const input = FilteredSearchContainer.container.querySelector('.filtered-search');
tokensContainer.insertBefore(li, input.parentElement);
}
......@@ -77,14 +79,14 @@ class FilteredSearchVisualTokens {
const addVisualTokenElement = FilteredSearchVisualTokens.addVisualTokenElement;
if (isLastVisualTokenValid) {
addVisualTokenElement(tokenName, tokenValue);
addVisualTokenElement(tokenName, tokenValue, false);
} else {
const previousTokenName = lastVisualToken.querySelector('.name').innerText;
const tokensContainer = document.querySelector('.tokens-container');
const tokensContainer = FilteredSearchContainer.container.querySelector('.tokens-container');
tokensContainer.removeChild(lastVisualToken);
const value = tokenValue || tokenName;
addVisualTokenElement(previousTokenName, value);
addVisualTokenElement(previousTokenName, value, false);
}
}
......@@ -129,7 +131,7 @@ class FilteredSearchVisualTokens {
}
static tokenizeInput() {
const input = document.querySelector('.filtered-search');
const input = FilteredSearchContainer.container.querySelector('.filtered-search');
const { isLastVisualTokenValid } =
gl.FilteredSearchVisualTokens.getLastVisualTokenBeforeInput();
......@@ -145,7 +147,7 @@ class FilteredSearchVisualTokens {
}
static editToken(token) {
const input = document.querySelector('.filtered-search');
const input = FilteredSearchContainer.container.querySelector('.filtered-search');
FilteredSearchVisualTokens.tokenizeInput();
......@@ -157,7 +159,7 @@ class FilteredSearchVisualTokens {
const name = token.querySelector('.name');
const value = token.querySelector('.value');
if (token.classList.contains('filtered-search-token')) {
if (token.classList.contains('filtered-search-token') && value) {
FilteredSearchVisualTokens.addFilterVisualToken(name.innerText);
input.value = value.innerText;
} else {
......@@ -174,9 +176,9 @@ class FilteredSearchVisualTokens {
}
static moveInputToTheRight() {
const input = document.querySelector('.filtered-search');
const input = FilteredSearchContainer.container.querySelector('.filtered-search');
const inputLi = input.parentElement;
const tokenContainer = document.querySelector('.tokens-container');
const tokenContainer = FilteredSearchContainer.container.querySelector('.tokens-container');
FilteredSearchVisualTokens.tokenizeInput();
......
/* eslint-disable func-names, space-before-function-paren, wrap-iife, no-var, one-var, one-var-declaration-per-line, no-param-reassign, quotes, quote-props, prefer-template, comma-dangle, max-len */
(function() {
this.Flash = (function() {
window.Flash = (function() {
var hideFlash;
hideFlash = function() {
......@@ -38,5 +38,4 @@
}
return Flash;
})();
}).call(window);
})();
......@@ -5,16 +5,13 @@ import emojiAliases from 'emojis/aliases.json';
import { glEmojiTag } from '~/behaviors/gl_emoji';
// Creates the variables for setting up GFM auto-completion
(function() {
if (window.gl == null) {
window.gl = {};
}
window.gl = window.gl || {};
function sanitize(str) {
function sanitize(str) {
return str.replace(/<(?:.|\n)*?>/gm, '');
}
}
window.gl.GfmAutoComplete = {
window.gl.GfmAutoComplete = {
dataSources: {},
defaultLoadingData: ['loading'],
cachedData: {},
......@@ -390,5 +387,4 @@ import { glEmojiTag } from '~/behaviors/gl_emoji';
return dataToInspect &&
(dataToInspect === loadingState || dataToInspect.name === loadingState);
}
};
}).call(window);
};
/* eslint-disable func-names, space-before-function-paren, no-var, one-var, one-var-declaration-per-line, prefer-rest-params, max-len, vars-on-top, wrap-iife, no-unused-vars, quotes, no-shadow, no-cond-assign, prefer-arrow-callback, no-return-assign, no-else-return, camelcase, comma-dangle, no-lonely-if, guard-for-in, no-restricted-syntax, consistent-return, prefer-template, no-param-reassign, no-loop-func, no-mixed-operators */
/* global fuzzaldrinPlus */
(function() {
var GitLabDropdown, GitLabDropdownFilter, GitLabDropdownRemote,
var GitLabDropdown, GitLabDropdownFilter, GitLabDropdownRemote,
bind = function(fn, me) { return function() { return fn.apply(me, arguments); }; },
indexOf = [].indexOf || function(item) { for (var i = 0, l = this.length; i < l; i += 1) { if (i in this && this[i] === item) return i; } return -1; };
GitLabDropdownFilter = (function() {
GitLabDropdownFilter = (function() {
var ARROW_KEY_CODES, BLUR_KEYCODES, HAS_VALUE_CLASS;
BLUR_KEYCODES = [27, 40];
......@@ -135,9 +134,9 @@
};
return GitLabDropdownFilter;
})();
})();
GitLabDropdownRemote = (function() {
GitLabDropdownRemote = (function() {
function GitLabDropdownRemote(dataEndpoint, options) {
this.dataEndpoint = dataEndpoint;
this.options = options;
......@@ -187,9 +186,9 @@
};
return GitLabDropdownRemote;
})();
})();
GitLabDropdown = (function() {
GitLabDropdown = (function() {
var ACTIVE_CLASS, FILTER_INPUT, INDETERMINATE_CLASS, LOADING_CLASS, PAGE_TWO_CLASS, NON_SELECTABLE_CLASSES, SELECTABLE_CLASSES, CURSOR_SELECT_SCROLL_PADDING, currentIndex;
LOADING_CLASS = "is-loading";
......@@ -838,13 +837,12 @@
};
return GitLabDropdown;
})();
})();
$.fn.glDropdown = function(opts) {
$.fn.glDropdown = function(opts) {
return this.each(function() {
if (!$.data(this, 'glDropdown')) {
return $.data(this, 'glDropdown', new GitLabDropdown(this, opts));
}
});
};
}).call(window);
};
/* eslint-disable no-param-reassign */
((global) => {
/*
/**
* This class overrides the browser's validation error bubbles, displaying custom
* error messages for invalid fields instead. To begin validating any form, add the
* class `gl-show-field-errors` to the form element, and ensure error messages are
......@@ -41,22 +39,22 @@
* // Error message now injected here
* </form>
*
* */
*/
/*
/**
* Regex Patterns in use:
*
* Only alphanumeric: : "[a-zA-Z0-9]+"
* No special characters : "[a-zA-Z0-9-_]+",
*
* */
*/
const errorMessageClass = 'gl-field-error';
const inputErrorClass = 'gl-field-error-outline';
const errorAnchorSelector = '.gl-field-error-anchor';
const ignoreInputSelector = '.gl-field-error-ignore';
const errorMessageClass = 'gl-field-error';
const inputErrorClass = 'gl-field-error-outline';
const errorAnchorSelector = '.gl-field-error-anchor';
const ignoreInputSelector = '.gl-field-error-ignore';
class GlFieldError {
class GlFieldError {
constructor({ input, formErrors }) {
this.inputElement = $(input);
this.inputDomElement = this.inputElement.get(0);
......@@ -158,7 +156,7 @@
this.scopedSiblings.hide();
this.fieldErrorElement.hide();
}
}
}
global.GlFieldError = GlFieldError;
})(window.gl || (window.gl = {}));
window.gl = window.gl || {};
window.gl.GlFieldError = GlFieldError;
......@@ -2,10 +2,9 @@
require('./gl_field_error');
((global) => {
const customValidationFlag = 'gl-field-error-ignore';
const customValidationFlag = 'gl-field-error-ignore';
class GlFieldErrors {
class GlFieldErrors {
constructor(form) {
this.form = $(form);
this.state = {
......@@ -22,7 +21,7 @@ require('./gl_field_error');
this.state.inputs = this.form.find(validateSelectors).toArray()
.filter((input) => !input.classList.contains(customValidationFlag))
.map((input) => new global.GlFieldError({ input, formErrors: this }));
.map((input) => new window.gl.GlFieldError({ input, formErrors: this }));
this.form.on('submit', this.catchInvalidFormSubmit);
}
......@@ -42,7 +41,7 @@ require('./gl_field_error');
const firstInvalid = this.state.inputs.filter((input) => !input.inputDomElement.validity.valid)[0];
firstInvalid.inputElement.focus();
}
}
}
global.GlFieldErrors = GlFieldErrors;
})(window.gl || (window.gl = {}));
window.gl = window.gl || {};
window.gl.GlFieldErrors = GlFieldErrors;
......@@ -3,10 +3,9 @@
/* global DropzoneInput */
/* global autosize */
(() => {
const global = window.gl || (window.gl = {});
window.gl = window.gl || {};
function GLForm(form) {
function GLForm(form) {
this.form = form;
this.textarea = this.form.find('textarea.js-gfm-input');
// Before we start, we should clean up any previous data for this form
......@@ -14,15 +13,15 @@
// Setup the form
this.setupForm();
this.form.data('gl-form', this);
}
}
GLForm.prototype.destroy = function() {
GLForm.prototype.destroy = function() {
// Clean form listeners
this.clearEventListeners();
return this.form.data('gl-form', null);
};
};
GLForm.prototype.setupForm = function() {
GLForm.prototype.setupForm = function() {
var isNewForm;
isNewForm = this.form.is(':not(.gfm-form)');
this.form.removeClass('js-new-note-form');
......@@ -42,9 +41,9 @@
this.form.find('.js-note-discard').hide();
this.form.show();
if (this.isAutosizeable) this.setupAutosize();
};
};
GLForm.prototype.setupAutosize = function () {
GLForm.prototype.setupAutosize = function () {
this.textarea.off('autosize:resized')
.on('autosize:resized', this.setHeightData.bind(this));
......@@ -55,13 +54,13 @@
autosize(this.textarea);
this.textarea.css('resize', 'vertical');
}, 0);
};
};
GLForm.prototype.setHeightData = function () {
GLForm.prototype.setHeightData = function () {
this.textarea.data('height', this.textarea.outerHeight());
};
};
GLForm.prototype.destroyAutosize = function () {
GLForm.prototype.destroyAutosize = function () {
const outerHeight = this.textarea.outerHeight();
if (this.textarea.data('height') === outerHeight) return;
......@@ -71,22 +70,21 @@
this.textarea.data('height', outerHeight);
this.textarea.outerHeight(outerHeight);
this.textarea.css('max-height', window.outerHeight);
};
};
GLForm.prototype.clearEventListeners = function() {
GLForm.prototype.clearEventListeners = function() {
this.textarea.off('focus');
this.textarea.off('blur');
return gl.text.removeListeners(this.form);
};
};
GLForm.prototype.addEventListeners = function() {
GLForm.prototype.addEventListeners = function() {
this.textarea.on('focus', function() {
return $(this).closest('.md-area').addClass('is-focused');
});
return this.textarea.on('blur', function() {
return $(this).closest('.md-area').removeClass('is-focused');
});
};
};
global.GLForm = GLForm;
})();
window.gl.GLForm = GLForm;
/* eslint-disable func-names, space-before-function-paren, wrap-iife, quotes, no-var, one-var, one-var-declaration-per-line, no-useless-escape, max-len */
(function() {
this.GroupAvatar = (function() {
window.GroupAvatar = (function() {
function GroupAvatar() {
$('.js-choose-group-avatar-button').on("click", function() {
var form;
......@@ -16,5 +16,4 @@
}
return GroupAvatar;
})();
}).call(window);
})();
/* eslint-disable func-names, object-shorthand, comma-dangle, wrap-iife, space-before-function-paren, no-param-reassign, max-len */
(function(global) {
class GroupLabelSubscription {
class GroupLabelSubscription {
constructor(container) {
const $container = $(container);
this.$dropdown = $container.find('.dropdown');
......@@ -47,7 +46,7 @@
this.$subscribeButtons.toggleClass('hidden');
this.$unsubscribeButtons.toggleClass('hidden');
}
}
}
global.GroupLabelSubscription = GroupLabelSubscription;
})(window.gl || (window.gl = {}));
window.gl = window.gl || {};
window.gl.GroupLabelSubscription = GroupLabelSubscription;
const GROUP_LIMIT = 2;
export default class GroupName {
constructor() {
this.titleContainer = document.querySelector('.title');
this.groups = document.querySelectorAll('.group-path');
this.groupTitle = document.querySelector('.group-title');
this.toggle = null;
this.isHidden = false;
this.init();
}
init() {
if (this.groups.length > GROUP_LIMIT) {
this.groups[this.groups.length - 1].classList.remove('hidable');
this.addToggle();
}
this.render();
}
addToggle() {
const header = document.querySelector('.header-content');
this.toggle = document.createElement('button');
this.toggle.className = 'text-expander group-name-toggle';
this.toggle.setAttribute('aria-label', 'Toggle full path');
this.toggle.innerHTML = '...';
this.toggle.addEventListener('click', this.toggleGroups.bind(this));
header.insertBefore(this.toggle, this.titleContainer);
this.toggleGroups();
}
toggleGroups() {
this.isHidden = !this.isHidden;
this.groupTitle.classList.toggle('is-hidden');
}
render() {
this.titleContainer.classList.remove('initializing');
}
}
/* eslint-disable func-names, space-before-function-paren, no-var, wrap-iife, one-var, camelcase, one-var-declaration-per-line, quotes, object-shorthand, prefer-arrow-callback, comma-dangle, consistent-return, yoda, prefer-rest-params, prefer-spread, no-unused-vars, prefer-template, max-len */
/* global Api */
(function() {
var slice = [].slice;
var slice = [].slice;
this.GroupsSelect = (function() {
window.GroupsSelect = (function() {
function GroupsSelect() {
$('.ajax-groups-select').each((function(_this) {
return function(i, select) {
......@@ -67,5 +66,4 @@
};
return GroupsSelect;
})();
}).call(window);
})();
/* eslint-disable wrap-iife, func-names, space-before-function-paren, prefer-arrow-callback, no-var, max-len */
(function() {
$(document).on('todo:toggle', function(e, count) {
/* eslint-disable func-names, space-before-function-paren, prefer-arrow-callback, no-var */
$(document).on('todo:toggle', function(e, count) {
var $todoPendingCount = $('.todos-pending-count');
$todoPendingCount.text(gl.text.highCountTrim(count));
$todoPendingCount.toggleClass('hidden', count === 0);
});
})();
});
......@@ -353,31 +353,17 @@
return;
}
if ($('html').hasClass('issue-boards-page') && !$dropdown.hasClass('js-issue-board-sidebar') &&
!$dropdown.closest('.add-issues-modal').length) {
boardsModel = gl.issueBoards.BoardsStore.state.filters;
} else if ($dropdown.closest('.add-issues-modal').length) {
if ($dropdown.closest('.add-issues-modal').length) {
boardsModel = gl.issueBoards.ModalStore.store.filter;
}
if (boardsModel) {
if (label.isAny) {
boardsModel['label_name'] = [];
}
else if ($el.hasClass('is-active')) {
} else if ($el.hasClass('is-active')) {
boardsModel['label_name'].push(label.title);
}
else {
var filters = boardsModel['label_name'];
filters = filters.filter(function (filteredLabel) {
return filteredLabel !== label.title;
});
boardsModel['label_name'] = filters;
}
if (!$dropdown.closest('.add-issues-modal').length) {
gl.issueBoards.BoardsStore.updateFiltersUrl();
}
e.preventDefault();
return;
}
......
......@@ -67,17 +67,7 @@ require('vendor/jquery.scrollTo');
}
LineHighlighter.prototype.bindEvents = function() {
$('#blob-content-holder').on('mousedown', 'a[data-line-number]', this.clickHandler);
// While it may seem odd to bind to the mousedown event and then throw away
// the click event, there is a method to our madness.
//
// If not done this way, the line number anchor will sometimes keep its
// active state even when the event is cancelled, resulting in an ugly border
// around the link and/or a persisted underline text decoration.
$('#blob-content-holder').on('click', 'a[data-line-number]', function(event) {
event.preventDefault();
event.stopPropagation();
});
$('#blob-content-holder').on('click', 'a[data-line-number]', this.clickHandler);
};
LineHighlighter.prototype.clickHandler = function(event) {
......
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
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