summaryrefslogtreecommitdiff
path: root/app/assets/javascripts
diff options
context:
space:
mode:
authorFilipa Lacerda <filipa@gitlab.com>2017-04-19 14:48:33 +0100
committerFilipa Lacerda <filipa@gitlab.com>2017-04-19 14:48:33 +0100
commit6c3038e8714a2e9b959cf09cd0658b48118d2ddf (patch)
tree2850e1f499099b2e31837589e02fda6b9cd570d7 /app/assets/javascripts
parentdbbcb32cfef8d9304afe648bbe13e8b98fc1ee30 (diff)
parenta9da37434af6d44c5f851affd4bd69b370760e8e (diff)
downloadgitlab-ce-6c3038e8714a2e9b959cf09cd0658b48118d2ddf.tar.gz
Merge branch 'master' into 30985-cancel-pipelines
* master: Remove special naming of pipelines folder remove changelog (not needed) Fix active user count add spec and changelog Add migration to remove orphaned notification settings Improve container registry repository path specs Fix duplicated container repository names update textarea height and refocus when attaching files Remove IIFEs in filtered_search_bundle.js Remove IIFEs from diff_notes_bundle.js
Diffstat (limited to 'app/assets/javascripts')
-rw-r--r--app/assets/javascripts/commit/pipelines/pipelines_table.js10
-rw-r--r--app/assets/javascripts/diff_notes/components/comment_resolve_btn.js100
-rw-r--r--app/assets/javascripts/diff_notes/components/diff_note_avatars.js260
-rw-r--r--app/assets/javascripts/diff_notes/components/jump_to_discussion.js310
-rw-r--r--app/assets/javascripts/diff_notes/components/new_issue_for_discussion.js42
-rw-r--r--app/assets/javascripts/diff_notes/components/resolve_btn.js198
-rw-r--r--app/assets/javascripts/diff_notes/components/resolve_count.js36
-rw-r--r--app/assets/javascripts/diff_notes/components/resolve_discussion_btn.js96
-rw-r--r--app/assets/javascripts/diff_notes/mixins/discussion.js50
-rw-r--r--app/assets/javascripts/diff_notes/services/resolve.js108
-rw-r--r--app/assets/javascripts/diff_notes/stores/comments.js90
-rw-r--r--app/assets/javascripts/dropzone_input.js11
-rw-r--r--app/assets/javascripts/filtered_search/dropdown_hint.js122
-rw-r--r--app/assets/javascripts/filtered_search/dropdown_non_user.js76
-rw-r--r--app/assets/javascripts/filtered_search/dropdown_user.js106
-rw-r--r--app/assets/javascripts/filtered_search/dropdown_utils.js298
-rw-r--r--app/assets/javascripts/filtered_search/filtered_search_dropdown.js194
-rw-r--r--app/assets/javascripts/filtered_search/filtered_search_dropdown_manager.js298
-rw-r--r--app/assets/javascripts/filtered_search/filtered_search_manager.js776
-rw-r--r--app/assets/javascripts/filtered_search/filtered_search_token_keys.js168
-rw-r--r--app/assets/javascripts/filtered_search/filtered_search_tokenizer.js100
-rw-r--r--app/assets/javascripts/pipelines/components/async_button.vue (renamed from app/assets/javascripts/vue_pipelines_index/components/async_button.vue)0
-rw-r--r--app/assets/javascripts/pipelines/components/empty_state.vue (renamed from app/assets/javascripts/vue_pipelines_index/components/empty_state.vue)0
-rw-r--r--app/assets/javascripts/pipelines/components/error_state.vue (renamed from app/assets/javascripts/vue_pipelines_index/components/error_state.vue)0
-rw-r--r--app/assets/javascripts/pipelines/components/nav_controls.js (renamed from app/assets/javascripts/vue_pipelines_index/components/nav_controls.js)0
-rw-r--r--app/assets/javascripts/pipelines/components/navigation_tabs.js (renamed from app/assets/javascripts/vue_pipelines_index/components/navigation_tabs.js)0
-rw-r--r--app/assets/javascripts/pipelines/components/pipeline_url.js (renamed from app/assets/javascripts/vue_pipelines_index/components/pipeline_url.js)0
-rw-r--r--app/assets/javascripts/pipelines/components/pipelines_actions.js (renamed from app/assets/javascripts/vue_pipelines_index/components/pipelines_actions.js)0
-rw-r--r--app/assets/javascripts/pipelines/components/pipelines_artifacts.js (renamed from app/assets/javascripts/vue_pipelines_index/components/pipelines_artifacts.js)0
-rw-r--r--app/assets/javascripts/pipelines/components/stage.js (renamed from app/assets/javascripts/vue_pipelines_index/components/stage.js)0
-rw-r--r--app/assets/javascripts/pipelines/components/status.js (renamed from app/assets/javascripts/vue_pipelines_index/components/status.js)0
-rw-r--r--app/assets/javascripts/pipelines/components/time_ago.js (renamed from app/assets/javascripts/vue_pipelines_index/components/time_ago.js)0
-rw-r--r--app/assets/javascripts/pipelines/event_hub.js (renamed from app/assets/javascripts/vue_pipelines_index/event_hub.js)0
-rw-r--r--app/assets/javascripts/pipelines/index.js (renamed from app/assets/javascripts/vue_pipelines_index/index.js)0
-rw-r--r--app/assets/javascripts/pipelines/pipelines.js (renamed from app/assets/javascripts/vue_pipelines_index/pipelines.js)0
-rw-r--r--app/assets/javascripts/pipelines/services/pipelines_service.js (renamed from app/assets/javascripts/vue_pipelines_index/services/pipelines_service.js)0
-rw-r--r--app/assets/javascripts/pipelines/stores/pipelines_store.js (renamed from app/assets/javascripts/vue_pipelines_index/stores/pipelines_store.js)0
-rw-r--r--app/assets/javascripts/vue_shared/components/pipelines_table_row.js14
38 files changed, 1714 insertions, 1749 deletions
diff --git a/app/assets/javascripts/commit/pipelines/pipelines_table.js b/app/assets/javascripts/commit/pipelines/pipelines_table.js
index 1d16c64e07e..7438faeadf4 100644
--- a/app/assets/javascripts/commit/pipelines/pipelines_table.js
+++ b/app/assets/javascripts/commit/pipelines/pipelines_table.js
@@ -1,11 +1,11 @@
import Vue from 'vue';
import Visibility from 'visibilityjs';
import PipelinesTableComponent from '../../vue_shared/components/pipelines_table';
-import PipelinesService from '../../vue_pipelines_index/services/pipelines_service';
-import PipelineStore from '../../vue_pipelines_index/stores/pipelines_store';
-import eventHub from '../../vue_pipelines_index/event_hub';
-import EmptyState from '../../vue_pipelines_index/components/empty_state.vue';
-import ErrorState from '../../vue_pipelines_index/components/error_state.vue';
+import PipelinesService from '../../pipelines/services/pipelines_service';
+import PipelineStore from '../../pipelines/stores/pipelines_store';
+import eventHub from '../../pipelines/event_hub';
+import EmptyState from '../../pipelines/components/empty_state.vue';
+import ErrorState from '../../pipelines/components/error_state.vue';
import '../../lib/utils/common_utils';
import '../../vue_shared/vue_resource_interceptor';
import Poll from '../../lib/utils/poll';
diff --git a/app/assets/javascripts/diff_notes/components/comment_resolve_btn.js b/app/assets/javascripts/diff_notes/components/comment_resolve_btn.js
index eb76b7d15fd..aed7cac4e62 100644
--- a/app/assets/javascripts/diff_notes/components/comment_resolve_btn.js
+++ b/app/assets/javascripts/diff_notes/components/comment_resolve_btn.js
@@ -3,65 +3,63 @@
import Vue from 'vue';
-(() => {
- const CommentAndResolveBtn = Vue.extend({
- props: {
- discussionId: String,
+const CommentAndResolveBtn = Vue.extend({
+ props: {
+ discussionId: String,
+ },
+ data() {
+ return {
+ textareaIsEmpty: true,
+ discussion: {},
+ };
+ },
+ computed: {
+ showButton: function () {
+ if (this.discussion) {
+ return this.discussion.isResolvable();
+ } else {
+ return false;
+ }
},
- data() {
- return {
- textareaIsEmpty: true,
- discussion: {},
- };
+ isDiscussionResolved: function () {
+ return this.discussion.isResolved();
},
- computed: {
- showButton: function () {
- if (this.discussion) {
- return this.discussion.isResolvable();
+ buttonText: function () {
+ if (this.isDiscussionResolved) {
+ if (this.textareaIsEmpty) {
+ return "Unresolve discussion";
} else {
- return false;
+ return "Comment & unresolve discussion";
}
- },
- isDiscussionResolved: function () {
- return this.discussion.isResolved();
- },
- buttonText: function () {
- if (this.isDiscussionResolved) {
- if (this.textareaIsEmpty) {
- return "Unresolve discussion";
- } else {
- return "Comment & unresolve discussion";
- }
+ } else {
+ if (this.textareaIsEmpty) {
+ return "Resolve discussion";
} else {
- if (this.textareaIsEmpty) {
- return "Resolve discussion";
- } else {
- return "Comment & resolve discussion";
- }
+ return "Comment & resolve discussion";
}
}
- },
- created() {
- if (this.discussionId) {
- this.discussion = CommentsStore.state[this.discussionId];
- }
- },
- mounted: function () {
- if (!this.discussionId) return;
+ }
+ },
+ created() {
+ if (this.discussionId) {
+ this.discussion = CommentsStore.state[this.discussionId];
+ }
+ },
+ mounted: function () {
+ if (!this.discussionId) return;
- const $textarea = $(`.js-discussion-note-form[data-discussion-id=${this.discussionId}] .note-textarea`);
- this.textareaIsEmpty = $textarea.val() === '';
+ const $textarea = $(`.js-discussion-note-form[data-discussion-id=${this.discussionId}] .note-textarea`);
+ this.textareaIsEmpty = $textarea.val() === '';
- $textarea.on('input.comment-and-resolve-btn', () => {
- this.textareaIsEmpty = $textarea.val() === '';
- });
- },
- destroyed: function () {
- if (!this.discussionId) return;
+ $textarea.on('input.comment-and-resolve-btn', () => {
+ this.textareaIsEmpty = $textarea.val() === '';
+ });
+ },
+ destroyed: function () {
+ if (!this.discussionId) return;
- $(`.js-discussion-note-form[data-discussion-id=${this.discussionId}] .note-textarea`).off('input.comment-and-resolve-btn');
- }
- });
+ $(`.js-discussion-note-form[data-discussion-id=${this.discussionId}] .note-textarea`).off('input.comment-and-resolve-btn');
+ }
+});
- Vue.component('comment-and-resolve-btn', CommentAndResolveBtn);
-})(window);
+Vue.component('comment-and-resolve-btn', CommentAndResolveBtn);
diff --git a/app/assets/javascripts/diff_notes/components/diff_note_avatars.js b/app/assets/javascripts/diff_notes/components/diff_note_avatars.js
index 0297add94d5..f3a688fbf2f 100644
--- a/app/assets/javascripts/diff_notes/components/diff_note_avatars.js
+++ b/app/assets/javascripts/diff_notes/components/diff_note_avatars.js
@@ -4,155 +4,153 @@
import Vue from 'vue';
import collapseIcon from '../icons/collapse_icon.svg';
-(() => {
- const DiffNoteAvatars = Vue.extend({
- props: ['discussionId'],
- data() {
- return {
- isVisible: false,
- lineType: '',
- storeState: CommentsStore.state,
- shownAvatars: 3,
- collapseIcon,
- };
- },
- template: `
- <div class="diff-comment-avatar-holders"
- v-show="notesCount !== 0">
- <div v-if="!isVisible">
- <img v-for="note in notesSubset"
- class="avatar diff-comment-avatar has-tooltip js-diff-comment-avatar"
- width="19"
- height="19"
- role="button"
- data-container="body"
- data-placement="top"
- data-html="true"
- :data-line-type="lineType"
- :title="note.authorName + ': ' + note.noteTruncated"
- :src="note.authorAvatar"
- @click="clickedAvatar($event)" />
- <span v-if="notesCount > shownAvatars"
- class="diff-comments-more-count has-tooltip js-diff-comment-avatar"
- data-container="body"
- data-placement="top"
- ref="extraComments"
- role="button"
- :data-line-type="lineType"
- :title="extraNotesTitle"
- @click="clickedAvatar($event)">{{ moreText }}</span>
- </div>
- <button class="diff-notes-collapse js-diff-comment-avatar"
- type="button"
- aria-label="Show comments"
+const DiffNoteAvatars = Vue.extend({
+ props: ['discussionId'],
+ data() {
+ return {
+ isVisible: false,
+ lineType: '',
+ storeState: CommentsStore.state,
+ shownAvatars: 3,
+ collapseIcon,
+ };
+ },
+ template: `
+ <div class="diff-comment-avatar-holders"
+ v-show="notesCount !== 0">
+ <div v-if="!isVisible">
+ <img v-for="note in notesSubset"
+ class="avatar diff-comment-avatar has-tooltip js-diff-comment-avatar"
+ width="19"
+ height="19"
+ role="button"
+ data-container="body"
+ data-placement="top"
+ data-html="true"
+ :data-line-type="lineType"
+ :title="note.authorName + ': ' + note.noteTruncated"
+ :src="note.authorAvatar"
+ @click="clickedAvatar($event)" />
+ <span v-if="notesCount > shownAvatars"
+ class="diff-comments-more-count has-tooltip js-diff-comment-avatar"
+ data-container="body"
+ data-placement="top"
+ ref="extraComments"
+ role="button"
:data-line-type="lineType"
- @click="clickedAvatar($event)"
- v-if="isVisible"
- v-html="collapseIcon">
- </button>
+ :title="extraNotesTitle"
+ @click="clickedAvatar($event)">{{ moreText }}</span>
</div>
- `,
- mounted() {
+ <button class="diff-notes-collapse js-diff-comment-avatar"
+ type="button"
+ aria-label="Show comments"
+ :data-line-type="lineType"
+ @click="clickedAvatar($event)"
+ v-if="isVisible"
+ v-html="collapseIcon">
+ </button>
+ </div>
+ `,
+ mounted() {
+ this.$nextTick(() => {
+ this.addNoCommentClass();
+ this.setDiscussionVisible();
+
+ this.lineType = $(this.$el).closest('.diff-line-num').hasClass('old_line') ? 'old' : 'new';
+ });
+
+ $(document).on('toggle.comments', () => {
this.$nextTick(() => {
- this.addNoCommentClass();
this.setDiscussionVisible();
-
- this.lineType = $(this.$el).closest('.diff-line-num').hasClass('old_line') ? 'old' : 'new';
});
-
- $(document).on('toggle.comments', () => {
+ });
+ },
+ destroyed() {
+ $(document).off('toggle.comments');
+ },
+ watch: {
+ storeState: {
+ handler() {
this.$nextTick(() => {
- this.setDiscussionVisible();
+ $('.has-tooltip', this.$el).tooltip('fixTitle');
+
+ // We need to add/remove a class to an element that is outside the Vue instance
+ this.addNoCommentClass();
});
- });
- },
- destroyed() {
- $(document).off('toggle.comments');
- },
- watch: {
- storeState: {
- handler() {
- this.$nextTick(() => {
- $('.has-tooltip', this.$el).tooltip('fixTitle');
-
- // We need to add/remove a class to an element that is outside the Vue instance
- this.addNoCommentClass();
- });
- },
- deep: true,
},
+ deep: true,
},
- computed: {
- notesSubset() {
- let notes = [];
-
- if (this.discussion) {
- notes = Object.keys(this.discussion.notes)
- .slice(0, this.shownAvatars)
- .map(noteId => this.discussion.notes[noteId]);
- }
-
- return notes;
- },
- extraNotesTitle() {
- if (this.discussion) {
- const extra = this.discussion.notesCount() - this.shownAvatars;
+ },
+ computed: {
+ notesSubset() {
+ let notes = [];
+
+ if (this.discussion) {
+ notes = Object.keys(this.discussion.notes)
+ .slice(0, this.shownAvatars)
+ .map(noteId => this.discussion.notes[noteId]);
+ }
+
+ return notes;
+ },
+ extraNotesTitle() {
+ if (this.discussion) {
+ const extra = this.discussion.notesCount() - this.shownAvatars;
- return `${extra} more comment${extra > 1 ? 's' : ''}`;
- }
+ return `${extra} more comment${extra > 1 ? 's' : ''}`;
+ }
- return '';
- },
- discussion() {
- return this.storeState[this.discussionId];
- },
- notesCount() {
- if (this.discussion) {
- return this.discussion.notesCount();
- }
+ return '';
+ },
+ discussion() {
+ return this.storeState[this.discussionId];
+ },
+ notesCount() {
+ if (this.discussion) {
+ return this.discussion.notesCount();
+ }
- return 0;
- },
- moreText() {
- const plusSign = this.notesCount < 100 ? '+' : '';
+ return 0;
+ },
+ moreText() {
+ const plusSign = this.notesCount < 100 ? '+' : '';
- return `${plusSign}${this.notesCount - this.shownAvatars}`;
- },
+ return `${plusSign}${this.notesCount - this.shownAvatars}`;
},
- methods: {
- clickedAvatar(e) {
- notes.addDiffNote(e);
+ },
+ methods: {
+ clickedAvatar(e) {
+ notes.addDiffNote(e);
- // Toggle the active state of the toggle all button
- this.toggleDiscussionsToggleState();
+ // Toggle the active state of the toggle all button
+ this.toggleDiscussionsToggleState();
- this.$nextTick(() => {
- this.setDiscussionVisible();
+ this.$nextTick(() => {
+ this.setDiscussionVisible();
- $('.has-tooltip', this.$el).tooltip('fixTitle');
- $('.has-tooltip', this.$el).tooltip('hide');
- });
- },
- addNoCommentClass() {
- const notesCount = this.notesCount;
+ $('.has-tooltip', this.$el).tooltip('fixTitle');
+ $('.has-tooltip', this.$el).tooltip('hide');
+ });
+ },
+ addNoCommentClass() {
+ const notesCount = this.notesCount;
- $(this.$el).closest('.js-avatar-container')
- .toggleClass('js-no-comment-btn', notesCount > 0)
- .nextUntil('.js-avatar-container')
- .toggleClass('js-no-comment-btn', notesCount > 0);
- },
- toggleDiscussionsToggleState() {
- const $notesHolders = $(this.$el).closest('.code').find('.notes_holder');
- const $visibleNotesHolders = $notesHolders.filter(':visible');
- const $toggleDiffCommentsBtn = $(this.$el).closest('.diff-file').find('.js-toggle-diff-comments');
+ $(this.$el).closest('.js-avatar-container')
+ .toggleClass('js-no-comment-btn', notesCount > 0)
+ .nextUntil('.js-avatar-container')
+ .toggleClass('js-no-comment-btn', notesCount > 0);
+ },
+ toggleDiscussionsToggleState() {
+ const $notesHolders = $(this.$el).closest('.code').find('.notes_holder');
+ const $visibleNotesHolders = $notesHolders.filter(':visible');
+ const $toggleDiffCommentsBtn = $(this.$el).closest('.diff-file').find('.js-toggle-diff-comments');
- $toggleDiffCommentsBtn.toggleClass('active', $notesHolders.length === $visibleNotesHolders.length);
- },
- setDiscussionVisible() {
- this.isVisible = $(`.diffs .notes[data-discussion-id="${this.discussion.id}"]`).is(':visible');
- },
+ $toggleDiffCommentsBtn.toggleClass('active', $notesHolders.length === $visibleNotesHolders.length);
+ },
+ setDiscussionVisible() {
+ this.isVisible = $(`.diffs .notes[data-discussion-id="${this.discussion.id}"]`).is(':visible');
},
- });
+ },
+});
- Vue.component('diff-note-avatars', DiffNoteAvatars);
-})();
+Vue.component('diff-note-avatars', DiffNoteAvatars);
diff --git a/app/assets/javascripts/diff_notes/components/jump_to_discussion.js b/app/assets/javascripts/diff_notes/components/jump_to_discussion.js
index 8edc45130fc..8a0fd3bb4a7 100644
--- a/app/assets/javascripts/diff_notes/components/jump_to_discussion.js
+++ b/app/assets/javascripts/diff_notes/components/jump_to_discussion.js
@@ -4,192 +4,190 @@
import Vue from 'vue';
-(() => {
- const JumpToDiscussion = Vue.extend({
- mixins: [DiscussionMixins],
- props: {
- discussionId: String
+const JumpToDiscussion = Vue.extend({
+ mixins: [DiscussionMixins],
+ props: {
+ discussionId: String
+ },
+ data: function () {
+ return {
+ discussions: CommentsStore.state,
+ discussion: {},
+ };
+ },
+ computed: {
+ allResolved: function () {
+ return this.unresolvedDiscussionCount === 0;
},
- data: function () {
- return {
- discussions: CommentsStore.state,
- discussion: {},
- };
- },
- computed: {
- allResolved: function () {
- return this.unresolvedDiscussionCount === 0;
- },
- showButton: function () {
- if (this.discussionId) {
- if (this.unresolvedDiscussionCount > 1) {
- return true;
- } else {
- return this.discussionId !== this.lastResolvedId;
- }
+ showButton: function () {
+ if (this.discussionId) {
+ if (this.unresolvedDiscussionCount > 1) {
+ return true;
} else {
- return this.unresolvedDiscussionCount >= 1;
+ return this.discussionId !== this.lastResolvedId;
}
- },
- lastResolvedId: function () {
- let lastId;
- for (const discussionId in this.discussions) {
- const discussion = this.discussions[discussionId];
-
- if (!discussion.isResolved()) {
- lastId = discussion.id;
- }
- }
- return lastId;
+ } else {
+ return this.unresolvedDiscussionCount >= 1;
}
},
- methods: {
- jumpToNextUnresolvedDiscussion: function () {
- let discussionsSelector;
- let discussionIdsInScope;
- let firstUnresolvedDiscussionId;
- let nextUnresolvedDiscussionId;
- let activeTab = window.mrTabs.currentAction;
- let hasDiscussionsToJumpTo = true;
- let jumpToFirstDiscussion = !this.discussionId;
-
- const discussionIdsForElements = function(elements) {
- return elements.map(function() {
- return $(this).attr('data-discussion-id');
- }).toArray();
- };
-
- const discussions = this.discussions;
-
- if (activeTab === 'diffs') {
- discussionsSelector = '.diffs .notes[data-discussion-id]';
- discussionIdsInScope = discussionIdsForElements($(discussionsSelector));
-
- let unresolvedDiscussionCount = 0;
-
- for (let i = 0; i < discussionIdsInScope.length; i += 1) {
- const discussionId = discussionIdsInScope[i];
- const discussion = discussions[discussionId];
- if (discussion && !discussion.isResolved()) {
- unresolvedDiscussionCount += 1;
- }
- }
+ lastResolvedId: function () {
+ let lastId;
+ for (const discussionId in this.discussions) {
+ const discussion = this.discussions[discussionId];
- if (this.discussionId && !this.discussion.isResolved()) {
- // If this is the last unresolved discussion on the diffs tab,
- // there are no discussions to jump to.
- if (unresolvedDiscussionCount === 1) {
- hasDiscussionsToJumpTo = false;
- }
- } else {
- // If there are no unresolved discussions on the diffs tab at all,
- // there are no discussions to jump to.
- if (unresolvedDiscussionCount === 0) {
- hasDiscussionsToJumpTo = false;
- }
- }
- } else if (activeTab !== 'notes') {
- // If we are on the commits or builds tabs,
- // there are no discussions to jump to.
- hasDiscussionsToJumpTo = false;
+ if (!discussion.isResolved()) {
+ lastId = discussion.id;
}
+ }
+ return lastId;
+ }
+ },
+ methods: {
+ jumpToNextUnresolvedDiscussion: function () {
+ let discussionsSelector;
+ let discussionIdsInScope;
+ let firstUnresolvedDiscussionId;
+ let nextUnresolvedDiscussionId;
+ let activeTab = window.mrTabs.currentAction;
+ let hasDiscussionsToJumpTo = true;
+ let jumpToFirstDiscussion = !this.discussionId;
+
+ const discussionIdsForElements = function(elements) {
+ return elements.map(function() {
+ return $(this).attr('data-discussion-id');
+ }).toArray();
+ };
- if (!hasDiscussionsToJumpTo) {
- // If there are no discussions to jump to on the current page,
- // switch to the notes tab and jump to the first disucssion there.
- window.mrTabs.activateTab('notes');
- activeTab = 'notes';
- jumpToFirstDiscussion = true;
- }
+ const discussions = this.discussions;
- if (activeTab === 'notes') {
- discussionsSelector = '.discussion[data-discussion-id]';
- discussionIdsInScope = discussionIdsForElements($(discussionsSelector));
- }
+ if (activeTab === 'diffs') {
+ discussionsSelector = '.diffs .notes[data-discussion-id]';
+ discussionIdsInScope = discussionIdsForElements($(discussionsSelector));
+
+ let unresolvedDiscussionCount = 0;
- let currentDiscussionFound = false;
for (let i = 0; i < discussionIdsInScope.length; i += 1) {
const discussionId = discussionIdsInScope[i];
const discussion = discussions[discussionId];
+ if (discussion && !discussion.isResolved()) {
+ unresolvedDiscussionCount += 1;
+ }
+ }
- if (!discussion) {
- // Discussions for comments on commits in this MR don't have a resolved status.
- continue;
+ if (this.discussionId && !this.discussion.isResolved()) {
+ // If this is the last unresolved discussion on the diffs tab,
+ // there are no discussions to jump to.
+ if (unresolvedDiscussionCount === 1) {
+ hasDiscussionsToJumpTo = false;
+ }
+ } else {
+ // If there are no unresolved discussions on the diffs tab at all,
+ // there are no discussions to jump to.
+ if (unresolvedDiscussionCount === 0) {
+ hasDiscussionsToJumpTo = false;
}
+ }
+ } else if (activeTab !== 'notes') {
+ // If we are on the commits or builds tabs,
+ // there are no discussions to jump to.
+ hasDiscussionsToJumpTo = false;
+ }
- if (!firstUnresolvedDiscussionId && !discussion.isResolved()) {
- firstUnresolvedDiscussionId = discussionId;
+ if (!hasDiscussionsToJumpTo) {
+ // If there are no discussions to jump to on the current page,
+ // switch to the notes tab and jump to the first disucssion there.
+ window.mrTabs.activateTab('notes');
+ activeTab = 'notes';
+ jumpToFirstDiscussion = true;
+ }
- if (jumpToFirstDiscussion) {
- break;
- }
+ if (activeTab === 'notes') {
+ discussionsSelector = '.discussion[data-discussion-id]';
+ discussionIdsInScope = discussionIdsForElements($(discussionsSelector));
+ }
+
+ let currentDiscussionFound = false;
+ for (let i = 0; i < discussionIdsInScope.length; i += 1) {
+ const discussionId = discussionIdsInScope[i];
+ const discussion = discussions[discussionId];
+
+ if (!discussion) {
+ // Discussions for comments on commits in this MR don't have a resolved status.
+ continue;
+ }
+
+ if (!firstUnresolvedDiscussionId && !discussion.isResolved()) {
+ firstUnresolvedDiscussionId = discussionId;
+
+ if (jumpToFirstDiscussion) {
+ break;
}
+ }
- if (!jumpToFirstDiscussion) {
- if (currentDiscussionFound) {
- if (!discussion.isResolved()) {
- nextUnresolvedDiscussionId = discussionId;
- break;
- }
- else {
- continue;
- }
+ if (!jumpToFirstDiscussion) {
+ if (currentDiscussionFound) {
+ if (!discussion.isResolved()) {
+ nextUnresolvedDiscussionId = discussionId;
+ break;
}
-
- if (discussionId === this.discussionId) {
- currentDiscussionFound = true;
+ else {
+ continue;
}
}
+
+ if (discussionId === this.discussionId) {
+ currentDiscussionFound = true;
+ }
}
+ }
- nextUnresolvedDiscussionId = nextUnresolvedDiscussionId || firstUnresolvedDiscussionId;
+ nextUnresolvedDiscussionId = nextUnresolvedDiscussionId || firstUnresolvedDiscussionId;
- if (!nextUnresolvedDiscussionId) {
- return;
- }
+ if (!nextUnresolvedDiscussionId) {
+ return;
+ }
- let $target = $(`${discussionsSelector}[data-discussion-id="${nextUnresolvedDiscussionId}"]`);
+ let $target = $(`${discussionsSelector}[data-discussion-id="${nextUnresolvedDiscussionId}"]`);
- if (activeTab === 'notes') {
- $target = $target.closest('.note-discussion');
+ if (activeTab === 'notes') {
+ $target = $target.closest('.note-discussion');
- // If the next discussion is closed, toggle it open.
- if ($target.find('.js-toggle-content').is(':hidden')) {
- $target.find('.js-toggle-button i').trigger('click');
+ // If the next discussion is closed, toggle it open.
+ if ($target.find('.js-toggle-content').is(':hidden')) {
+ $target.find('.js-toggle-button i').trigger('click');
+ }
+ } else if (activeTab === 'diffs') {
+ // Resolved discussions are hidden in the diffs tab by default.
+ // If they are marked unresolved on the notes tab, they will still be hidden on the diffs tab.
+ // When jumping between unresolved discussions on the diffs tab, we show them.
+ $target.closest(".content").show();
+
+ $target = $target.closest("tr.notes_holder");
+ $target.show();
+
+ // If we are on the diffs tab, we don't scroll to the discussion itself, but to
+ // 4 diff lines above it: the line the discussion was in response to + 3 context
+ let prevEl;
+ for (let i = 0; i < 4; i += 1) {
+ prevEl = $target.prev();
+
+ // If the discussion doesn't have 4 lines above it, we'll have to do with fewer.
+ if (!prevEl.hasClass("line_holder")) {
+ break;
}
- } else if (activeTab === 'diffs') {
- // Resolved discussions are hidden in the diffs tab by default.
- // If they are marked unresolved on the notes tab, they will still be hidden on the diffs tab.
- // When jumping between unresolved discussions on the diffs tab, we show them.
- $target.closest(".content").show();
-
- $target = $target.closest("tr.notes_holder");
- $target.show();
-
- // If we are on the diffs tab, we don't scroll to the discussion itself, but to
- // 4 diff lines above it: the line the discussion was in response to + 3 context
- let prevEl;
- for (let i = 0; i < 4; i += 1) {
- prevEl = $target.prev();
-
- // If the discussion doesn't have 4 lines above it, we'll have to do with fewer.
- if (!prevEl.hasClass("line_holder")) {
- break;
- }
- $target = prevEl;
- }
+ $target = prevEl;
}
-
- $.scrollTo($target, {
- offset: 0
- });
}
- },
- created() {
- this.discussion = this.discussions[this.discussionId];
- },
- });
- Vue.component('jump-to-discussion', JumpToDiscussion);
-})();
+ $.scrollTo($target, {
+ offset: 0
+ });
+ }
+ },
+ created() {
+ this.discussion = this.discussions[this.discussionId];
+ },
+});
+
+Vue.component('jump-to-discussion', JumpToDiscussion);
diff --git a/app/assets/javascripts/diff_notes/components/new_issue_for_discussion.js b/app/assets/javascripts/diff_notes/components/new_issue_for_discussion.js
index 8eb0e10b832..e0c09aa0eee 100644
--- a/app/assets/javascripts/diff_notes/components/new_issue_for_discussion.js
+++ b/app/assets/javascripts/diff_notes/components/new_issue_for_discussion.js
@@ -2,29 +2,27 @@
import Vue from 'vue';
-(() => {
- const NewIssueForDiscussion = Vue.extend({
- props: {
- discussionId: {
- type: String,
- required: true,
- },
+const NewIssueForDiscussion = Vue.extend({
+ props: {
+ discussionId: {
+ type: String,
+ required: true,
},
- data() {
- return {
- discussions: CommentsStore.state,
- };
+ },
+ data() {
+ return {
+ discussions: CommentsStore.state,
+ };
+ },
+ computed: {
+ discussion() {
+ return this.discussions[this.discussionId];
},
- computed: {
- discussion() {
- return this.discussions[this.discussionId];
- },
- showButton() {
- if (this.discussion) return !this.discussion.isResolved();
- return false;
- },
+ showButton() {
+ if (this.discussion) return !this.discussion.isResolved();
+ return false;
},
- });
+ },
+});
- Vue.component('new-issue-for-discussion-btn', NewIssueForDiscussion);
-})();
+Vue.component('new-issue-for-discussion-btn', NewIssueForDiscussion);
diff --git a/app/assets/javascripts/diff_notes/components/resolve_btn.js b/app/assets/javascripts/diff_notes/components/resolve_btn.js
index dcfc40c1013..8fafd13c6c2 100644
--- a/app/assets/javascripts/diff_notes/components/resolve_btn.js
+++ b/app/assets/javascripts/diff_notes/components/resolve_btn.js
@@ -5,117 +5,115 @@
import Vue from 'vue';
-(() => {
- const ResolveBtn = Vue.extend({
- props: {
- noteId: Number,
- discussionId: String,
- resolved: Boolean,
- canResolve: Boolean,
- resolvedBy: String,
- authorName: String,
- authorAvatar: String,
- noteTruncated: String,
+const ResolveBtn = Vue.extend({
+ props: {
+ noteId: Number,
+ discussionId: String,
+ resolved: Boolean,
+ canResolve: Boolean,
+ resolvedBy: String,
+ authorName: String,
+ authorAvatar: String,
+ noteTruncated: String,
+ },
+ data: function () {
+ return {
+ discussions: CommentsStore.state,
+ loading: false
+ };
+ },
+ watch: {
+ 'discussions': {
+ handler: 'updateTooltip',
+ deep: true
+ }
+ },
+ computed: {
+ discussion: function () {
+ return this.discussions[this.discussionId];
},
- data: function () {
- return {
- discussions: CommentsStore.state,
- loading: false
- };
+ note: function () {
+ return this.discussion ? this.discussion.getNote(this.noteId) : {};
},
- watch: {
- 'discussions': {
- handler: 'updateTooltip',
- deep: true
+ buttonText: function () {
+ if (this.isResolved) {
+ return `Resolved by ${this.resolvedByName}`;
+ } else if (this.canResolve) {
+ return 'Mark as resolved';
+ } else {
+ return 'Unable to resolve';
}
},
- computed: {
- discussion: function () {
- return this.discussions[this.discussionId];
- },
- note: function () {
- return this.discussion ? this.discussion.getNote(this.noteId) : {};
- },
- buttonText: function () {
- if (this.isResolved) {
- return `Resolved by ${this.resolvedByName}`;
- } else if (this.canResolve) {
- return 'Mark as resolved';
- } else {
- return 'Unable to resolve';
- }
- },
- isResolved: function () {
- if (this.note) {
- return this.note.resolved;
- } else {
- return false;
- }
- },
- resolvedByName: function () {
- return this.note.resolved_by;
- },
+ isResolved: function () {
+ if (this.note) {
+ return this.note.resolved;
+ } else {
+ return false;
+ }
+ },
+ resolvedByName: function () {
+ return this.note.resolved_by;
},
- methods: {
- updateTooltip: function () {
- this.$nextTick(() => {
- $(this.$refs.button)
- .tooltip('hide')
- .tooltip('fixTitle');
- });
- },
- resolve: function () {
- if (!this.canResolve) return;
+ },
+ methods: {
+ updateTooltip: function () {
+ this.$nextTick(() => {
+ $(this.$refs.button)
+ .tooltip('hide')
+ .tooltip('fixTitle');
+ });
+ },
+ resolve: function () {
+ if (!this.canResolve) return;
- let promise;
- this.loading = true;
+ let promise;
+ this.loading = true;
- if (this.isResolved) {
- promise = ResolveService
- .unresolve(this.noteId);
- } else {
- promise = ResolveService
- .resolve(this.noteId);
- }
+ if (this.isResolved) {
+ promise = ResolveService
+ .unresolve(this.noteId);
+ } else {
+ promise = ResolveService
+ .resolve(this.noteId);
+ }
- promise.then((response) => {
- this.loading = false;
+ promise.then((response) => {
+ this.loading = false;
- if (response.status === 200) {
- const data = response.json();
- const resolved_by = data ? data.resolved_by : null;
+ if (response.status === 200) {
+ const data = response.json();
+ const resolved_by = data ? data.resolved_by : null;
- CommentsStore.update(this.discussionId, this.noteId, !this.isResolved, resolved_by);
- this.discussion.updateHeadline(data);
- } else {
- new Flash('An error occurred when trying to resolve a comment. Please try again.', 'alert');
- }
+ CommentsStore.update(this.discussionId, this.noteId, !this.isResolved, resolved_by);
+ this.discussion.updateHeadline(data);
+ } else {
+ new Flash('An error occurred when trying to resolve a comment. Please try again.', 'alert');
+ }
- this.updateTooltip();
- });
- }
- },
- mounted: function () {
- $(this.$refs.button).tooltip({
- container: 'body'
- });
- },
- beforeDestroy: function () {
- CommentsStore.delete(this.discussionId, this.noteId);
- },
- created: function () {
- CommentsStore.create({
- discussionId: this.discussionId,
- noteId: this.noteId,
- canResolve: this.canResolve,
- resolved: this.resolved,
- resolvedBy: this.resolvedBy,
- authorName: this.authorName,
- authorAvatar: this.authorAvatar,
- noteTruncated: this.noteTruncated,
+ this.updateTooltip();
});
}
- });
+ },
+ mounted: function () {
+ $(this.$refs.button).tooltip({
+ container: 'body'
+ });
+ },
+ beforeDestroy: function () {
+ CommentsStore.delete(this.discussionId, this.noteId);
+ },
+ created: function () {
+ CommentsStore.create({
+ discussionId: this.discussionId,
+ noteId: this.noteId,
+ canResolve: this.canResolve,
+ resolved: this.resolved,
+ resolvedBy: this.resolvedBy,
+ authorName: this.authorName,
+ authorAvatar: this.authorAvatar,
+ noteTruncated: this.noteTruncated,
+ });
+ }
+});
- Vue.component('resolve-btn', ResolveBtn);
-})();
+Vue.component('resolve-btn', ResolveBtn);
diff --git a/app/assets/javascripts/diff_notes/components/resolve_count.js b/app/assets/javascripts/diff_notes/components/resolve_count.js
index 27147ac6b5c..96e5a440357 100644
--- a/app/assets/javascripts/diff_notes/components/resolve_count.js
+++ b/app/assets/javascripts/diff_notes/components/resolve_count.js
@@ -4,24 +4,22 @@
import Vue from 'vue';
-((w) => {
- w.ResolveCount = Vue.extend({
- mixins: [DiscussionMixins],
- props: {
- loggedOut: Boolean
+window.ResolveCount = Vue.extend({
+ mixins: [DiscussionMixins],
+ props: {
+ loggedOut: Boolean
+ },
+ data: function () {
+ return {
+ discussions: CommentsStore.state
+ };
+ },
+ computed: {
+ allResolved: function () {
+ return this.resolvedDiscussionCount === this.discussionCount;
},
- data: function () {
- return {
- discussions: CommentsStore.state
- };
- },
- computed: {
- allResolved: function () {
- return this.resolvedDiscussionCount === this.discussionCount;
- },
- resolvedCountText() {
- return this.discussionCount === 1 ? 'discussion' : 'discussions';
- }
+ resolvedCountText() {
+ return this.discussionCount === 1 ? 'discussion' : 'discussions';
}
- });
-})(window);
+ }
+});
diff --git a/app/assets/javascripts/diff_notes/components/resolve_discussion_btn.js b/app/assets/javascripts/diff_notes/components/resolve_discussion_btn.js
index a964b7d0c6b..6a036e96171 100644
--- a/app/assets/javascripts/diff_notes/components/resolve_discussion_btn.js
+++ b/app/assets/javascripts/diff_notes/components/resolve_discussion_btn.js
@@ -4,59 +4,57 @@
import Vue from 'vue';
-(() => {
- const ResolveDiscussionBtn = Vue.extend({
- props: {
- discussionId: String,
- mergeRequestId: Number,
- canResolve: Boolean,
- },
- data: function() {
- return {
- discussion: {},
- };
+const ResolveDiscussionBtn = Vue.extend({
+ props: {
+ discussionId: String,
+ mergeRequestId: Number,
+ canResolve: Boolean,
+ },
+ data: function() {
+ return {
+ discussion: {},
+ };
+ },
+ computed: {
+ showButton: function () {
+ if (this.discussion) {
+ return this.discussion.isResolvable();
+ } else {
+ return false;
+ }
},
- computed: {
- showButton: function () {
- if (this.discussion) {
- return this.discussion.isResolvable();
- } else {
- return false;
- }
- },
- isDiscussionResolved: function () {
- if (this.discussion) {
- return this.discussion.isResolved();
- } else {
- return false;
- }
- },
- buttonText: function () {
- if (this.isDiscussionResolved) {
- return "Unresolve discussion";
- } else {
- return "Resolve discussion";
- }
- },
- loading: function () {
- if (this.discussion) {
- return this.discussion.loading;
- } else {
- return false;
- }
+ isDiscussionResolved: function () {
+ if (this.discussion) {
+ return this.discussion.isResolved();
+ } else {
+ return false;
}
},
- methods: {
- resolve: function () {
- ResolveService.toggleResolveForDiscussion(this.mergeRequestId, this.discussionId);
+ buttonText: function () {
+ if (this.isDiscussionResolved) {
+ return "Unresolve discussion";
+ } else {
+ return "Resolve discussion";
}
},
- created: function () {
- CommentsStore.createDiscussion(this.discussionId, this.canResolve);
-
- this.discussion = CommentsStore.state[this.discussionId];
+ loading: function () {
+ if (this.discussion) {
+ return this.discussion.loading;
+ } else {
+ return false;
+ }
+ }
+ },
+ methods: {
+ resolve: function () {
+ ResolveService.toggleResolveForDiscussion(this.mergeRequestId, this.discussionId);
}
- });
+ },
+ created: function () {
+ CommentsStore.createDiscussion(this.discussionId, this.canResolve);
+
+ this.discussion = CommentsStore.state[this.discussionId];
+ }
+});
- Vue.component('resolve-discussion-btn', ResolveDiscussionBtn);
-})();
+Vue.component('resolve-discussion-btn', ResolveDiscussionBtn);
diff --git a/app/assets/javascripts/diff_notes/mixins/discussion.js b/app/assets/javascripts/diff_notes/mixins/discussion.js
index 3c08c222f46..36c4abf02cf 100644
--- a/app/assets/javascripts/diff_notes/mixins/discussion.js
+++ b/app/assets/javascripts/diff_notes/mixins/discussion.js
@@ -1,37 +1,35 @@
/* eslint-disable object-shorthand, func-names, guard-for-in, no-restricted-syntax, comma-dangle, no-param-reassign, max-len */
-((w) => {
- w.DiscussionMixins = {
- computed: {
- discussionCount: function () {
- return Object.keys(this.discussions).length;
- },
- resolvedDiscussionCount: function () {
- let resolvedCount = 0;
+window.DiscussionMixins = {
+ computed: {
+ discussionCount: function () {
+ return Object.keys(this.discussions).length;
+ },
+ resolvedDiscussionCount: function () {
+ let resolvedCount = 0;
- for (const discussionId in this.discussions) {
- const discussion = this.discussions[discussionId];
+ for (const discussionId in this.discussions) {
+ const discussion = this.discussions[discussionId];
- if (discussion.isResolved()) {
- resolvedCount += 1;
- }
+ if (discussion.isResolved()) {
+ resolvedCount += 1;
}
+ }
- return resolvedCount;
- },
- unresolvedDiscussionCount: function () {
- let unresolvedCount = 0;
+ return resolvedCount;
+ },
+ unresolvedDiscussionCount: function () {
+ let unresolvedCount = 0;
- for (const discussionId in this.discussions) {
- const discussion = this.discussions[discussionId];
+ for (const discussionId in this.discussions) {
+ const discussion = this.discussions[discussionId];
- if (!discussion.isResolved()) {
- unresolvedCount += 1;
- }
+ if (!discussion.isResolved()) {
+ unresolvedCount += 1;
}
-
- return unresolvedCount;
}
+
+ return unresolvedCount;
}
- };
-})(window);
+ }
+};
diff --git a/app/assets/javascripts/diff_notes/services/resolve.js b/app/assets/javascripts/diff_notes/services/resolve.js
index bfa4fc9037a..e1e2e3e93f9 100644
--- a/app/assets/javascripts/diff_notes/services/resolve.js
+++ b/app/assets/javascripts/diff_notes/services/resolve.js
@@ -9,76 +9,74 @@ require('../../vue_shared/vue_resource_interceptor');
Vue.use(VueResource);
-(() => {
- window.gl = window.gl || {};
+window.gl = window.gl || {};
- class ResolveServiceClass {
- constructor(root) {
- this.noteResource = Vue.resource(`${root}/notes{/noteId}/resolve`);
- this.discussionResource = Vue.resource(`${root}/merge_requests{/mergeRequestId}/discussions{/discussionId}/resolve`);
- }
-
- resolve(noteId) {
- return this.noteResource.save({ noteId }, {});
- }
+class ResolveServiceClass {
+ constructor(root) {
+ this.noteResource = Vue.resource(`${root}/notes{/noteId}/resolve`);
+ this.discussionResource = Vue.resource(`${root}/merge_requests{/mergeRequestId}/discussions{/discussionId}/resolve`);
+ }
- unresolve(noteId) {
- return this.noteResource.delete({ noteId }, {});
- }
+ resolve(noteId) {
+ return this.noteResource.save({ noteId }, {});
+ }
- toggleResolveForDiscussion(mergeRequestId, discussionId) {
- const discussion = CommentsStore.state[discussionId];
- const isResolved = discussion.isResolved();
- let promise;
+ unresolve(noteId) {
+ return this.noteResource.delete({ noteId }, {});
+ }
- if (isResolved) {
- promise = this.unResolveAll(mergeRequestId, discussionId);
- } else {
- promise = this.resolveAll(mergeRequestId, discussionId);
- }
+ toggleResolveForDiscussion(mergeRequestId, discussionId) {
+ const discussion = CommentsStore.state[discussionId];
+ const isResolved = discussion.isResolved();
+ let promise;
- promise.then((response) => {
- discussion.loading = false;
+ if (isResolved) {
+ promise = this.unResolveAll(mergeRequestId, discussionId);
+ } else {
+ promise = this.resolveAll(mergeRequestId, discussionId);
+ }
- if (response.status === 200) {
- const data = response.json();
- const resolved_by = data ? data.resolved_by : null;
+ promise.then((response) => {
+ discussion.loading = false;
- if (isResolved) {
- discussion.unResolveAllNotes();
- } else {
- discussion.resolveAllNotes(resolved_by);
- }
+ if (response.status === 200) {
+ const data = response.json();
+ const resolved_by = data ? data.resolved_by : null;
- discussion.updateHeadline(data);
+ if (isResolved) {
+ discussion.unResolveAllNotes();
} else {
- new Flash('An error occurred when trying to resolve a discussion. Please try again.', 'alert');
+ discussion.resolveAllNotes(resolved_by);
}
- });
- }
- resolveAll(mergeRequestId, discussionId) {
- const discussion = CommentsStore.state[discussionId];
+ discussion.updateHeadline(data);
+ } else {
+ new Flash('An error occurred when trying to resolve a discussion. Please try again.', 'alert');
+ }
+ });
+ }
- discussion.loading = true;
+ resolveAll(mergeRequestId, discussionId) {
+ const discussion = CommentsStore.state[discussionId];
- return this.discussionResource.save({
- mergeRequestId,
- discussionId
- }, {});
- }
+ discussion.loading = true;
+
+ return this.discussionResource.save({
+ mergeRequestId,
+ discussionId
+ }, {});
+ }
- unResolveAll(mergeRequestId, discussionId) {
- const discussion = CommentsStore.state[discussionId];
+ unResolveAll(mergeRequestId, discussionId) {
+ const discussion = CommentsStore.state[discussionId];
- discussion.loading = true;
+ discussion.loading = true;
- return this.discussionResource.delete({
- mergeRequestId,
- discussionId
- }, {});
- }
+ return this.discussionResource.delete({
+ mergeRequestId,
+ discussionId
+ }, {});
}
+}
- gl.DiffNotesResolveServiceClass = ResolveServiceClass;
-})();
+gl.DiffNotesResolveServiceClass = ResolveServiceClass;
diff --git a/app/assets/javascripts/diff_notes/stores/comments.js b/app/assets/javascripts/diff_notes/stores/comments.js
index e6cbda56c91..d802db7d3af 100644
--- a/app/assets/javascripts/diff_notes/stores/comments.js
+++ b/app/assets/javascripts/diff_notes/stores/comments.js
@@ -3,56 +3,54 @@
import Vue from 'vue';
-((w) => {
- w.CommentsStore = {
- state: {},
- get: function (discussionId, noteId) {
- return this.state[discussionId].getNote(noteId);
- },
- createDiscussion: function (discussionId, canResolve) {
- let discussion = this.state[discussionId];
- if (!this.state[discussionId]) {
- discussion = new DiscussionModel(discussionId);
- Vue.set(this.state, discussionId, discussion);
- }
+window.CommentsStore = {
+ state: {},
+ get: function (discussionId, noteId) {
+ return this.state[discussionId].getNote(noteId);
+ },
+ createDiscussion: function (discussionId, canResolve) {
+ let discussion = this.state[discussionId];
+ if (!this.state[discussionId]) {
+ discussion = new DiscussionModel(discussionId);
+ Vue.set(this.state, discussionId, discussion);
+ }
- if (canResolve !== undefined) {
- discussion.canResolve = canResolve;
- }
+ if (canResolve !== undefined) {
+ discussion.canResolve = canResolve;
+ }
- return discussion;
- },
- create: function (noteObj) {
- const discussion = this.createDiscussion(noteObj.discussionId);
+ return discussion;
+ },
+ create: function (noteObj) {
+ const discussion = this.createDiscussion(noteObj.discussionId);
+
+ discussion.createNote(noteObj);
+ },
+ update: function (discussionId, noteId, resolved, resolved_by) {
+ const discussion = this.state[discussionId];
+ const note = discussion.getNote(noteId);
+ note.resolved = resolved;
+ note.resolved_by = resolved_by;
+ },
+ delete: function (discussionId, noteId) {
+ const discussion = this.state[discussionId];
+ discussion.deleteNote(noteId);
+
+ if (discussion.notesCount() === 0) {
+ Vue.delete(this.state, discussionId);
+ }
+ },
+ unresolvedDiscussionIds: function () {
+ const ids = [];
- discussion.createNote(noteObj);
- },
- update: function (discussionId, noteId, resolved, resolved_by) {
- const discussion = this.state[discussionId];
- const note = discussion.getNote(noteId);
- note.resolved = resolved;
- note.resolved_by = resolved_by;
- },
- delete: function (discussionId, noteId) {
+ for (const discussionId in this.state) {
const discussion = this.state[discussionId];
- discussion.deleteNote(noteId);
- if (discussion.notesCount() === 0) {
- Vue.delete(this.state, discussionId);
+ if (!discussion.isResolved()) {
+ ids.push(discussion.id);
}
- },
- unresolvedDiscussionIds: function () {
- const ids = [];
-
- for (const discussionId in this.state) {
- const discussion = this.state[discussionId];
-
- if (!discussion.isResolved()) {
- ids.push(discussion.id);
- }
- }
-
- return ids;
}
- };
-})(window);
+
+ return ids;
+ }
+};
diff --git a/app/assets/javascripts/dropzone_input.js b/app/assets/javascripts/dropzone_input.js
index df0e3f46827..c5fbbdaf465 100644
--- a/app/assets/javascripts/dropzone_input.js
+++ b/app/assets/javascripts/dropzone_input.js
@@ -130,13 +130,15 @@ window.DropzoneInput = (function() {
var afterSelection, beforeSelection, caretEnd, caretStart, textEnd;
var formattedText = text;
if (shouldPad) formattedText += "\n\n";
- caretStart = $(child)[0].selectionStart;
- caretEnd = $(child)[0].selectionEnd;
+ const textarea = child.get(0);
+ caretStart = textarea.selectionStart;
+ caretEnd = textarea.selectionEnd;
textEnd = $(child).val().length;
beforeSelection = $(child).val().substring(0, caretStart);
afterSelection = $(child).val().substring(caretEnd, textEnd);
$(child).val(beforeSelection + formattedText + afterSelection);
- child.get(0).setSelectionRange(caretStart + formattedText.length, caretEnd + formattedText.length);
+ textarea.setSelectionRange(caretStart + formattedText.length, caretEnd + formattedText.length);
+ textarea.style.height = `${textarea.scrollHeight}px`;
return form_textarea.trigger("input");
};
getFilename = function(e) {
@@ -180,7 +182,7 @@ window.DropzoneInput = (function() {
};
insertToTextArea = function(filename, url) {
return $(child).val(function(index, val) {
- return val.replace("{{" + filename + "}}", url + "\n");
+ return val.replace("{{" + filename + "}}", url);
});
};
appendToTextArea = function(url) {
@@ -215,6 +217,7 @@ window.DropzoneInput = (function() {
form.find(".markdown-selector").click(function(e) {
e.preventDefault();
$(this).closest('.gfm-form').find('.div-dropzone').click();
+ form_textarea.focus();
});
}
diff --git a/app/assets/javascripts/filtered_search/dropdown_hint.js b/app/assets/javascripts/filtered_search/dropdown_hint.js
index 381c40c03d8..3e7a892756c 100644
--- a/app/assets/javascripts/filtered_search/dropdown_hint.js
+++ b/app/assets/javascripts/filtered_search/dropdown_hint.js
@@ -2,82 +2,80 @@ import Filter from '~/droplab/plugins/filter';
require('./filtered_search_dropdown');
-(() => {
- class DropdownHint extends gl.FilteredSearchDropdown {
- constructor(droplab, dropdown, input, filter) {
- super(droplab, dropdown, input, filter);
- this.config = {
- Filter: {
- template: 'hint',
- filterFunction: gl.DropdownUtils.filterHint.bind(null, input),
- },
- };
- }
-
- itemClicked(e) {
- const { selected } = e.detail;
+class DropdownHint extends gl.FilteredSearchDropdown {
+ constructor(droplab, dropdown, input, filter) {
+ super(droplab, dropdown, input, filter);
+ this.config = {
+ Filter: {
+ template: 'hint',
+ filterFunction: gl.DropdownUtils.filterHint.bind(null, input),
+ },
+ };
+ }
- if (selected.tagName === 'LI') {
- if (selected.hasAttribute('data-value')) {
- this.dismissDropdown();
- } else if (selected.getAttribute('data-action') === 'submit') {
- this.dismissDropdown();
- this.dispatchFormSubmitEvent();
- } else {
- const token = selected.querySelector('.js-filter-hint').innerText.trim();
- const tag = selected.querySelector('.js-filter-tag').innerText.trim();
+ itemClicked(e) {
+ const { selected } = e.detail;
- if (tag.length) {
- // Get previous input values in the input field and convert them into visual tokens
- const previousInputValues = this.input.value.split(' ');
- const searchTerms = [];
+ if (selected.tagName === 'LI') {
+ if (selected.hasAttribute('data-value')) {
+ this.dismissDropdown();
+ } else if (selected.getAttribute('data-action') === 'submit') {
+ this.dismissDropdown();
+ this.dispatchFormSubmitEvent();
+ } else {
+ const token = selected.querySelector('.js-filter-hint').innerText.trim();
+ const tag = selected.querySelector('.js-filter-tag').innerText.trim();
- previousInputValues.forEach((value, index) => {
- searchTerms.push(value);
+ if (tag.length) {
+ // Get previous input values in the input field and convert them into visual tokens
+ const previousInputValues = this.input.value.split(' ');
+ const searchTerms = [];
- if (index === previousInputValues.length - 1
- && token.indexOf(value.toLowerCase()) !== -1) {
- searchTerms.pop();
- }
- });
+ previousInputValues.forEach((value, index) => {
+ searchTerms.push(value);
- if (searchTerms.length > 0) {
- gl.FilteredSearchVisualTokens.addSearchVisualToken(searchTerms.join(' '));
+ if (index === previousInputValues.length - 1
+ && token.indexOf(value.toLowerCase()) !== -1) {
+ searchTerms.pop();
}
+ });
- gl.FilteredSearchDropdownManager.addWordToInput(token.replace(':', ''), '', false, this.container);
+ if (searchTerms.length > 0) {
+ gl.FilteredSearchVisualTokens.addSearchVisualToken(searchTerms.join(' '));
}
- this.dismissDropdown();
- this.dispatchInputEvent();
+
+ gl.FilteredSearchDropdownManager.addWordToInput(token.replace(':', ''), '', false, this.container);
}
+ this.dismissDropdown();
+ this.dispatchInputEvent();
}
}
+ }
- renderContent() {
- const dropdownData = [];
+ renderContent() {
+ const dropdownData = [];
- [].forEach.call(this.input.closest('.filtered-search-box-input-container').querySelectorAll('.dropdown-menu'), (dropdownMenu) => {
- const { icon, hint, tag, type } = dropdownMenu.dataset;
- if (icon && hint && tag) {
- dropdownData.push(
- Object.assign({
- icon: `fa-${icon}`,
- hint,
- tag: `&lt;${tag}&gt;`,
- }, type && { type }),
- );
- }
- });
+ [].forEach.call(this.input.closest('.filtered-search-box-input-container').querySelectorAll('.dropdown-menu'), (dropdownMenu) => {
+ const { icon, hint, tag, type } = dropdownMenu.dataset;
+ if (icon && hint && tag) {
+ dropdownData.push(
+ Object.assign({
+ icon: `fa-${icon}`,
+ hint,
+ tag: `&lt;${tag}&gt;`,
+ }, type && { type }),
+ );
+ }
+ });
- this.droplab.changeHookList(this.hookId, this.dropdown, [Filter], this.config);
- this.droplab.setData(this.hookId, dropdownData);
- }
+ this.droplab.changeHookList(this.hookId, this.dropdown, [Filter], this.config);
+ this.droplab.setData(this.hookId, dropdownData);
+ }
- init() {
- this.droplab.addHook(this.input, this.dropdown, [Filter], this.config).init();
- }
+ init() {
+ this.droplab.addHook(this.input, this.dropdown, [Filter], this.config).init();
}
+}
- window.gl = window.gl || {};
- gl.DropdownHint = DropdownHint;
-})();
+window.gl = window.gl || {};
+gl.DropdownHint = DropdownHint;
diff --git a/app/assets/javascripts/filtered_search/dropdown_non_user.js b/app/assets/javascripts/filtered_search/dropdown_non_user.js
index 6296965b911..982dc4b61be 100644
--- a/app/assets/javascripts/filtered_search/dropdown_non_user.js
+++ b/app/assets/javascripts/filtered_search/dropdown_non_user.js
@@ -5,48 +5,46 @@ import Filter from '~/droplab/plugins/filter';
require('./filtered_search_dropdown');
-(() => {
- class DropdownNonUser extends gl.FilteredSearchDropdown {
- constructor(droplab, dropdown, input, filter, endpoint, symbol) {
- super(droplab, dropdown, input, filter);
- this.symbol = symbol;
- this.config = {
- Ajax: {
- endpoint,
- method: 'setData',
- loadingTemplate: this.loadingTemplate,
- onError() {
- /* eslint-disable no-new */
- new Flash('An error occured fetching the dropdown data.');
- /* eslint-enable no-new */
- },
+class DropdownNonUser extends gl.FilteredSearchDropdown {
+ constructor(droplab, dropdown, input, filter, endpoint, symbol) {
+ super(droplab, dropdown, input, filter);
+ this.symbol = symbol;
+ this.config = {
+ Ajax: {
+ endpoint,
+ method: 'setData',
+ loadingTemplate: this.loadingTemplate,
+ onError() {
+ /* eslint-disable no-new */
+ new Flash('An error occured fetching the dropdown data.');
+ /* eslint-enable no-new */
},
- Filter: {
- filterFunction: gl.DropdownUtils.filterWithSymbol.bind(null, this.symbol, input),
- template: 'title',
- },
- };
- }
+ },
+ Filter: {
+ filterFunction: gl.DropdownUtils.filterWithSymbol.bind(null, this.symbol, input),
+ template: 'title',
+ },
+ };
+ }
- itemClicked(e) {
- super.itemClicked(e, (selected) => {
- const title = selected.querySelector('.js-data-value').innerText.trim();
- return `${this.symbol}${gl.DropdownUtils.getEscapedText(title)}`;
- });
- }
+ itemClicked(e) {
+ super.itemClicked(e, (selected) => {
+ const title = selected.querySelector('.js-data-value').innerText.trim();
+ return `${this.symbol}${gl.DropdownUtils.getEscapedText(title)}`;
+ });
+ }
- renderContent(forceShowList = false) {
- this.droplab
- .changeHookList(this.hookId, this.dropdown, [Ajax, Filter], this.config);
- super.renderContent(forceShowList);
- }
+ renderContent(forceShowList = false) {
+ this.droplab
+ .changeHookList(this.hookId, this.dropdown, [Ajax, Filter], this.config);
+ super.renderContent(forceShowList);
+ }
- init() {
- this.droplab
- .addHook(this.input, this.dropdown, [Ajax, Filter], this.config).init();
- }
+ init() {
+ this.droplab
+ .addHook(this.input, this.dropdown, [Ajax, Filter], this.config).init();
}
+}
- window.gl = window.gl || {};
- gl.DropdownNonUser = DropdownNonUser;
-})();
+window.gl = window.gl || {};
+gl.DropdownNonUser = DropdownNonUser;
diff --git a/app/assets/javascripts/filtered_search/dropdown_user.js b/app/assets/javascripts/filtered_search/dropdown_user.js
index 38b5d315bcf..74cec3d75fe 100644
--- a/app/assets/javascripts/filtered_search/dropdown_user.js
+++ b/app/assets/javascripts/filtered_search/dropdown_user.js
@@ -4,69 +4,67 @@ import AjaxFilter from '~/droplab/plugins/ajax_filter';
require('./filtered_search_dropdown');
-(() => {
- class DropdownUser extends gl.FilteredSearchDropdown {
- constructor(droplab, dropdown, input, filter) {
- super(droplab, dropdown, input, filter);
- this.config = {
- AjaxFilter: {
- endpoint: `${gon.relative_url_root || ''}/autocomplete/users.json`,
- searchKey: 'search',
- params: {
- per_page: 20,
- active: true,
- project_id: this.getProjectId(),
- current_user: true,
- },
- searchValueFunction: this.getSearchInput.bind(this),
- loadingTemplate: this.loadingTemplate,
- onError() {
- /* eslint-disable no-new */
- new Flash('An error occured fetching the dropdown data.');
- /* eslint-enable no-new */
- },
+class DropdownUser extends gl.FilteredSearchDropdown {
+ constructor(droplab, dropdown, input, filter) {
+ super(droplab, dropdown, input, filter);
+ this.config = {
+ AjaxFilter: {
+ endpoint: `${gon.relative_url_root || ''}/autocomplete/users.json`,
+ searchKey: 'search',
+ params: {
+ per_page: 20,
+ active: true,
+ project_id: this.getProjectId(),
+ current_user: true,
},
- };
- }
-
- itemClicked(e) {
- super.itemClicked(e,
- selected => selected.querySelector('.dropdown-light-content').innerText.trim());
- }
-
- renderContent(forceShowList = false) {
- this.droplab.changeHookList(this.hookId, this.dropdown, [AjaxFilter], this.config);
- super.renderContent(forceShowList);
- }
+ searchValueFunction: this.getSearchInput.bind(this),
+ loadingTemplate: this.loadingTemplate,
+ onError() {
+ /* eslint-disable no-new */
+ new Flash('An error occured fetching the dropdown data.');
+ /* eslint-enable no-new */
+ },
+ },
+ };
+ }
- getProjectId() {
- return this.input.getAttribute('data-project-id');
- }
+ itemClicked(e) {
+ super.itemClicked(e,
+ selected => selected.querySelector('.dropdown-light-content').innerText.trim());
+ }
- getSearchInput() {
- const query = gl.DropdownUtils.getSearchInput(this.input);
- const { lastToken } = gl.FilteredSearchTokenizer.processTokens(query);
+ renderContent(forceShowList = false) {
+ this.droplab.changeHookList(this.hookId, this.dropdown, [AjaxFilter], this.config);
+ super.renderContent(forceShowList);
+ }
- let value = lastToken || '';
+ getProjectId() {
+ return this.input.getAttribute('data-project-id');
+ }
- if (value[0] === '@') {
- value = value.slice(1);
- }
+ getSearchInput() {
+ const query = gl.DropdownUtils.getSearchInput(this.input);
+ const { lastToken } = gl.FilteredSearchTokenizer.processTokens(query);
- // Removes the first character if it is a quotation so that we can search
- // with multiple words
- if (value[0] === '"' || value[0] === '\'') {
- value = value.slice(1);
- }
+ let value = lastToken || '';
- return value;
+ if (value[0] === '@') {
+ value = value.slice(1);
}
- init() {
- this.droplab.addHook(this.input, this.dropdown, [AjaxFilter], this.config).init();
+ // Removes the first character if it is a quotation so that we can search
+ // with multiple words
+ if (value[0] === '"' || value[0] === '\'') {
+ value = value.slice(1);
}
+
+ return value;
+ }
+
+ init() {
+ this.droplab.addHook(this.input, this.dropdown, [AjaxFilter], this.config).init();
}
+}
- window.gl = window.gl || {};
- gl.DropdownUser = DropdownUser;
-})();
+window.gl = window.gl || {};
+gl.DropdownUser = DropdownUser;
diff --git a/app/assets/javascripts/filtered_search/dropdown_utils.js b/app/assets/javascripts/filtered_search/dropdown_utils.js
index 6c5c20447f7..bc7c1dffece 100644
--- a/app/assets/javascripts/filtered_search/dropdown_utils.js
+++ b/app/assets/javascripts/filtered_search/dropdown_utils.js
@@ -1,183 +1,181 @@
import FilteredSearchContainer from './container';
-(() => {
- class DropdownUtils {
- static getEscapedText(text) {
- let escapedText = text;
- const hasSpace = text.indexOf(' ') !== -1;
- const hasDoubleQuote = text.indexOf('"') !== -1;
-
- // Encapsulate value with quotes if it has spaces
- // Known side effect: values's with both single and double quotes
- // won't escape properly
- if (hasSpace) {
- if (hasDoubleQuote) {
- escapedText = `'${text}'`;
- } else {
- // Encapsulate singleQuotes or if it hasSpace
- escapedText = `"${text}"`;
- }
+class DropdownUtils {
+ static getEscapedText(text) {
+ let escapedText = text;
+ const hasSpace = text.indexOf(' ') !== -1;
+ const hasDoubleQuote = text.indexOf('"') !== -1;
+
+ // Encapsulate value with quotes if it has spaces
+ // Known side effect: values's with both single and double quotes
+ // won't escape properly
+ if (hasSpace) {
+ if (hasDoubleQuote) {
+ escapedText = `'${text}'`;
+ } else {
+ // Encapsulate singleQuotes or if it hasSpace
+ escapedText = `"${text}"`;
}
-
- return escapedText;
}
- static filterWithSymbol(filterSymbol, input, item) {
- const updatedItem = item;
- const searchInput = gl.DropdownUtils.getSearchInput(input);
+ return escapedText;
+ }
+
+ static filterWithSymbol(filterSymbol, input, item) {
+ const updatedItem = item;
+ const searchInput = gl.DropdownUtils.getSearchInput(input);
- const title = updatedItem.title.toLowerCase();
- let value = searchInput.toLowerCase();
- let symbol = '';
+ const title = updatedItem.title.toLowerCase();
+ let value = searchInput.toLowerCase();
+ let symbol = '';
- // Remove the symbol for filter
- if (value[0] === filterSymbol) {
- symbol = value[0];
- value = value.slice(1);
- }
+ // Remove the symbol for filter
+ if (value[0] === filterSymbol) {
+ symbol = value[0];
+ value = value.slice(1);
+ }
- // Removes the first character if it is a quotation so that we can search
- // with multiple words
- if ((value[0] === '"' || value[0] === '\'') && title.indexOf(' ') !== -1) {
- value = value.slice(1);
- }
+ // Removes the first character if it is a quotation so that we can search
+ // with multiple words
+ if ((value[0] === '"' || value[0] === '\'') && title.indexOf(' ') !== -1) {
+ value = value.slice(1);
+ }
+
+ // Eg. filterSymbol = ~ for labels
+ const matchWithoutSymbol = symbol === filterSymbol && title.indexOf(value) !== -1;
+ const match = title.indexOf(`${symbol}${value}`) !== -1;
- // Eg. filterSymbol = ~ for labels
- const matchWithoutSymbol = symbol === filterSymbol && title.indexOf(value) !== -1;
- const match = title.indexOf(`${symbol}${value}`) !== -1;
+ updatedItem.droplab_hidden = !match && !matchWithoutSymbol;
- updatedItem.droplab_hidden = !match && !matchWithoutSymbol;
+ return updatedItem;
+ }
- return updatedItem;
+ static filterHint(input, item) {
+ const updatedItem = item;
+ 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 (lastKey) {
+ const split = lastKey.split(':');
+ const tokenName = split[0].split(' ').last();
+
+ const match = updatedItem.hint.indexOf(tokenName.toLowerCase()) === -1;
+ updatedItem.droplab_hidden = tokenName ? match : false;
}
- static filterHint(input, item) {
- const updatedItem = item;
- 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 (lastKey) {
- const split = lastKey.split(':');
- const tokenName = split[0].split(' ').last();
-
- const match = updatedItem.hint.indexOf(tokenName.toLowerCase()) === -1;
- updatedItem.droplab_hidden = tokenName ? match : false;
- }
+ return updatedItem;
+ }
+
+ static setDataValueIfSelected(filter, selected) {
+ const dataValue = selected.getAttribute('data-value');
- return updatedItem;
+ if (dataValue) {
+ gl.FilteredSearchDropdownManager.addWordToInput(filter, dataValue, true);
}
- static setDataValueIfSelected(filter, selected) {
- const dataValue = selected.getAttribute('data-value');
+ // Return boolean based on whether it was set
+ return dataValue !== null;
+ }
- if (dataValue) {
- gl.FilteredSearchDropdownManager.addWordToInput(filter, dataValue, true);
- }
+ // Determines the full search query (visual tokens + input)
+ static getSearchQuery(untilInput = false) {
+ const container = FilteredSearchContainer.container;
+ const tokens = [].slice.call(container.querySelectorAll('.tokens-container li'));
+ const values = [];
- // Return boolean based on whether it was set
- return dataValue !== null;
+ if (untilInput) {
+ const inputIndex = _.findIndex(tokens, t => t.classList.contains('input-token'));
+ // Add one to include input-token to the tokens array
+ tokens.splice(inputIndex + 1);
}
- // Determines the full search query (visual tokens + input)
- static getSearchQuery(untilInput = false) {
- const container = FilteredSearchContainer.container;
- const tokens = [].slice.call(container.querySelectorAll('.tokens-container li'));
- const values = [];
+ tokens.forEach((token) => {
+ if (token.classList.contains('js-visual-token')) {
+ const name = token.querySelector('.name');
+ const value = token.querySelector('.value');
+ const symbol = value && value.dataset.symbol ? value.dataset.symbol : '';
+ let valueText = '';
- if (untilInput) {
- const inputIndex = _.findIndex(tokens, t => t.classList.contains('input-token'));
- // Add one to include input-token to the tokens array
- tokens.splice(inputIndex + 1);
- }
+ if (value && value.innerText) {
+ valueText = value.innerText;
+ }
- tokens.forEach((token) => {
- if (token.classList.contains('js-visual-token')) {
- const name = token.querySelector('.name');
- const value = token.querySelector('.value');
- const symbol = value && value.dataset.symbol ? value.dataset.symbol : '';
- let valueText = '';
-
- if (value && value.innerText) {
- valueText = value.innerText;
- }
-
- if (token.className.indexOf('filtered-search-token') !== -1) {
- values.push(`${name.innerText.toLowerCase()}:${symbol}${valueText}`);
- } else {
- values.push(name.innerText);
- }
- } else if (token.classList.contains('input-token')) {
- const { isLastVisualTokenValid } =
- gl.FilteredSearchVisualTokens.getLastVisualTokenBeforeInput();
-
- const input = FilteredSearchContainer.container.querySelector('.filtered-search');
- const inputValue = input && input.value;
-
- if (isLastVisualTokenValid) {
- values.push(inputValue);
- } else {
- const previous = values.pop();
- values.push(`${previous}${inputValue}`);
- }
+ if (token.className.indexOf('filtered-search-token') !== -1) {
+ values.push(`${name.innerText.toLowerCase()}:${symbol}${valueText}`);
+ } else {
+ values.push(name.innerText);
}
- });
+ } else if (token.classList.contains('input-token')) {
+ const { isLastVisualTokenValid } =
+ gl.FilteredSearchVisualTokens.getLastVisualTokenBeforeInput();
- return values
- .map(value => value.trim())
- .join(' ');
- }
+ const input = FilteredSearchContainer.container.querySelector('.filtered-search');
+ const inputValue = input && input.value;
- static getSearchInput(filteredSearchInput) {
- const inputValue = filteredSearchInput.value;
- const { right } = gl.DropdownUtils.getInputSelectionPosition(filteredSearchInput);
+ if (isLastVisualTokenValid) {
+ values.push(inputValue);
+ } else {
+ const previous = values.pop();
+ values.push(`${previous}${inputValue}`);
+ }
+ }
+ });
- return inputValue.slice(0, right);
- }
+ return values
+ .map(value => value.trim())
+ .join(' ');
+ }
- static getInputSelectionPosition(input) {
- const selectionStart = input.selectionStart;
- let inputValue = input.value;
- // Replace all spaces inside quote marks with underscores
- // (will continue to match entire string until an end quote is found if any)
- // This helps with matching the beginning & end of a token:key
- inputValue = inputValue.replace(/(('[^']*'{0,1})|("[^"]*"{0,1})|:\s+)/g, str => str.replace(/\s/g, '_'));
-
- // Get the right position for the word selected
- // Regex matches first space
- let right = inputValue.slice(selectionStart).search(/\s/);
-
- if (right >= 0) {
- right += selectionStart;
- } else if (right < 0) {
- right = inputValue.length;
- }
+ static getSearchInput(filteredSearchInput) {
+ const inputValue = filteredSearchInput.value;
+ const { right } = gl.DropdownUtils.getInputSelectionPosition(filteredSearchInput);
- // Get the left position for the word selected
- // Regex matches last non-whitespace character
- let left = inputValue.slice(0, right).search(/\S+$/);
+ return inputValue.slice(0, right);
+ }
- if (selectionStart === 0) {
- left = 0;
- } else if (selectionStart === inputValue.length && left < 0) {
- left = inputValue.length;
- } else if (left < 0) {
- left = selectionStart;
- }
+ static getInputSelectionPosition(input) {
+ const selectionStart = input.selectionStart;
+ let inputValue = input.value;
+ // Replace all spaces inside quote marks with underscores
+ // (will continue to match entire string until an end quote is found if any)
+ // This helps with matching the beginning & end of a token:key
+ inputValue = inputValue.replace(/(('[^']*'{0,1})|("[^"]*"{0,1})|:\s+)/g, str => str.replace(/\s/g, '_'));
+
+ // Get the right position for the word selected
+ // Regex matches first space
+ let right = inputValue.slice(selectionStart).search(/\s/);
+
+ if (right >= 0) {
+ right += selectionStart;
+ } else if (right < 0) {
+ right = inputValue.length;
+ }
+
+ // Get the left position for the word selected
+ // Regex matches last non-whitespace character
+ let left = inputValue.slice(0, right).search(/\S+$/);
- return {
- left,
- right,
- };
+ if (selectionStart === 0) {
+ left = 0;
+ } else if (selectionStart === inputValue.length && left < 0) {
+ left = inputValue.length;
+ } else if (left < 0) {
+ left = selectionStart;
}
+
+ return {
+ left,
+ right,
+ };
}
+}
- window.gl = window.gl || {};
- gl.DropdownUtils = DropdownUtils;
-})();
+window.gl = window.gl || {};
+gl.DropdownUtils = DropdownUtils;
diff --git a/app/assets/javascripts/filtered_search/filtered_search_dropdown.js b/app/assets/javascripts/filtered_search/filtered_search_dropdown.js
index d58eeeebf81..4209ca0d6e2 100644
--- a/app/assets/javascripts/filtered_search/filtered_search_dropdown.js
+++ b/app/assets/javascripts/filtered_search/filtered_search_dropdown.js
@@ -1,124 +1,122 @@
-(() => {
- const DATA_DROPDOWN_TRIGGER = 'data-dropdown-trigger';
-
- class FilteredSearchDropdown {
- constructor(droplab, dropdown, input, filter) {
- this.droplab = droplab;
- this.hookId = input && input.id;
- this.input = input;
- this.filter = filter;
- this.dropdown = dropdown;
- this.loadingTemplate = `<div class="filter-dropdown-loading">
- <i class="fa fa-spinner fa-spin"></i>
- </div>`;
- this.bindEvents();
- }
-
- bindEvents() {
- this.itemClickedWrapper = this.itemClicked.bind(this);
- this.dropdown.addEventListener('click.dl', this.itemClickedWrapper);
- }
+const DATA_DROPDOWN_TRIGGER = 'data-dropdown-trigger';
+
+class FilteredSearchDropdown {
+ constructor(droplab, dropdown, input, filter) {
+ this.droplab = droplab;
+ this.hookId = input && input.id;
+ this.input = input;
+ this.filter = filter;
+ this.dropdown = dropdown;
+ this.loadingTemplate = `<div class="filter-dropdown-loading">
+ <i class="fa fa-spinner fa-spin"></i>
+ </div>`;
+ this.bindEvents();
+ }
- unbindEvents() {
- this.dropdown.removeEventListener('click.dl', this.itemClickedWrapper);
- }
+ bindEvents() {
+ this.itemClickedWrapper = this.itemClicked.bind(this);
+ this.dropdown.addEventListener('click.dl', this.itemClickedWrapper);
+ }
- getCurrentHook() {
- return this.droplab.hooks.filter(h => h.id === this.hookId)[0] || null;
- }
+ unbindEvents() {
+ this.dropdown.removeEventListener('click.dl', this.itemClickedWrapper);
+ }
- itemClicked(e, getValueFunction) {
- const { selected } = e.detail;
+ getCurrentHook() {
+ return this.droplab.hooks.filter(h => h.id === this.hookId)[0] || null;
+ }
- if (selected.tagName === 'LI' && selected.innerHTML) {
- const dataValueSet = gl.DropdownUtils.setDataValueIfSelected(this.filter, selected);
+ itemClicked(e, getValueFunction) {
+ const { selected } = e.detail;
- if (!dataValueSet) {
- const value = getValueFunction(selected);
- gl.FilteredSearchDropdownManager.addWordToInput(this.filter, value, true);
- }
+ if (selected.tagName === 'LI' && selected.innerHTML) {
+ const dataValueSet = gl.DropdownUtils.setDataValueIfSelected(this.filter, selected);
- this.resetFilters();
- this.dismissDropdown();
- this.dispatchInputEvent();
+ if (!dataValueSet) {
+ const value = getValueFunction(selected);
+ gl.FilteredSearchDropdownManager.addWordToInput(this.filter, value, true);
}
- }
- setAsDropdown() {
- this.input.setAttribute(DATA_DROPDOWN_TRIGGER, `#${this.dropdown.id}`);
+ this.resetFilters();
+ this.dismissDropdown();
+ this.dispatchInputEvent();
}
+ }
- setOffset(offset = 0) {
- if (window.innerWidth > 480) {
- this.dropdown.style.left = `${offset}px`;
- } else {
- this.dropdown.style.left = '0px';
- }
+ setAsDropdown() {
+ this.input.setAttribute(DATA_DROPDOWN_TRIGGER, `#${this.dropdown.id}`);
+ }
+
+ setOffset(offset = 0) {
+ if (window.innerWidth > 480) {
+ this.dropdown.style.left = `${offset}px`;
+ } else {
+ this.dropdown.style.left = '0px';
}
+ }
- renderContent(forceShowList = false) {
- const currentHook = this.getCurrentHook();
- if (forceShowList && currentHook && currentHook.list.hidden) {
- currentHook.list.show();
- }
+ renderContent(forceShowList = false) {
+ const currentHook = this.getCurrentHook();
+ if (forceShowList && currentHook && currentHook.list.hidden) {
+ currentHook.list.show();
}
+ }
- render(forceRenderContent = false, forceShowList = false) {
- this.setAsDropdown();
+ render(forceRenderContent = false, forceShowList = false) {
+ this.setAsDropdown();
- const currentHook = this.getCurrentHook();
- const firstTimeInitialized = currentHook === null;
+ const currentHook = this.getCurrentHook();
+ const firstTimeInitialized = currentHook === null;
- if (firstTimeInitialized || forceRenderContent) {
- this.renderContent(forceShowList);
- } else if (currentHook.list.list.id !== this.dropdown.id) {
- this.renderContent(forceShowList);
- }
+ if (firstTimeInitialized || forceRenderContent) {
+ this.renderContent(forceShowList);
+ } else if (currentHook.list.list.id !== this.dropdown.id) {
+ this.renderContent(forceShowList);
}
+ }
- dismissDropdown() {
- // Focusing on the input will dismiss dropdown
- // (default droplab functionality)
- this.input.focus();
- }
+ dismissDropdown() {
+ // Focusing on the input will dismiss dropdown
+ // (default droplab functionality)
+ this.input.focus();
+ }
- dispatchInputEvent() {
- // Propogate input change to FilteredSearchDropdownManager
- // so that it can determine which dropdowns to open
- this.input.dispatchEvent(new CustomEvent('input', {
- bubbles: true,
- cancelable: true,
- }));
- }
+ dispatchInputEvent() {
+ // Propogate input change to FilteredSearchDropdownManager
+ // so that it can determine which dropdowns to open
+ this.input.dispatchEvent(new CustomEvent('input', {
+ bubbles: true,
+ cancelable: true,
+ }));
+ }
- dispatchFormSubmitEvent() {
- // dispatchEvent() is necessary as form.submit() does not
- // trigger event handlers
- this.input.form.dispatchEvent(new Event('submit'));
- }
+ dispatchFormSubmitEvent() {
+ // dispatchEvent() is necessary as form.submit() does not
+ // trigger event handlers
+ this.input.form.dispatchEvent(new Event('submit'));
+ }
- hideDropdown() {
- const currentHook = this.getCurrentHook();
- if (currentHook) {
- currentHook.list.hide();
- }
+ hideDropdown() {
+ const currentHook = this.getCurrentHook();
+ if (currentHook) {
+ currentHook.list.hide();
}
+ }
- resetFilters() {
- const hook = this.getCurrentHook();
-
- if (hook) {
- const data = hook.list.data || [];
- const results = data.map((o) => {
- const updated = o;
- updated.droplab_hidden = false;
- return updated;
- });
- hook.list.render(results);
- }
+ resetFilters() {
+ const hook = this.getCurrentHook();
+
+ if (hook) {
+ const data = hook.list.data || [];
+ const results = data.map((o) => {
+ const updated = o;
+ updated.droplab_hidden = false;
+ return updated;
+ });
+ hook.list.render(results);
}
}
+}
- window.gl = window.gl || {};
- gl.FilteredSearchDropdown = FilteredSearchDropdown;
-})();
+window.gl = window.gl || {};
+gl.FilteredSearchDropdown = FilteredSearchDropdown;
diff --git a/app/assets/javascripts/filtered_search/filtered_search_dropdown_manager.js b/app/assets/javascripts/filtered_search/filtered_search_dropdown_manager.js
index ec481b9ef97..49a6cd1ac77 100644
--- a/app/assets/javascripts/filtered_search/filtered_search_dropdown_manager.js
+++ b/app/assets/javascripts/filtered_search/filtered_search_dropdown_manager.js
@@ -1,191 +1,189 @@
import DropLab from '~/droplab/drop_lab';
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 = this.container.querySelector('.filtered-search');
- this.page = page;
-
- this.setupMapping();
-
- this.cleanupWrapper = this.cleanup.bind(this);
- document.addEventListener('beforeunload', this.cleanupWrapper);
+class FilteredSearchDropdownManager {
+ constructor(baseEndpoint = '', page) {
+ this.container = FilteredSearchContainer.container;
+ this.baseEndpoint = baseEndpoint.replace(/\/$/, '');
+ this.tokenizer = gl.FilteredSearchTokenizer;
+ this.filteredSearchTokenKeys = gl.FilteredSearchTokenKeys;
+ this.filteredSearchInput = this.container.querySelector('.filtered-search');
+ this.page = page;
+
+ this.setupMapping();
+
+ this.cleanupWrapper = this.cleanup.bind(this);
+ document.addEventListener('beforeunload', this.cleanupWrapper);
+ }
+
+ cleanup() {
+ if (this.droplab) {
+ this.droplab.destroy();
+ this.droplab = null;
}
- cleanup() {
- if (this.droplab) {
- this.droplab.destroy();
- this.droplab = null;
- }
+ this.setupMapping();
- this.setupMapping();
+ document.removeEventListener('beforeunload', this.cleanupWrapper);
+ }
- document.removeEventListener('beforeunload', this.cleanupWrapper);
- }
+ setupMapping() {
+ this.mapping = {
+ author: {
+ reference: null,
+ gl: 'DropdownUser',
+ element: this.container.querySelector('#js-dropdown-author'),
+ },
+ assignee: {
+ reference: null,
+ gl: 'DropdownUser',
+ element: this.container.querySelector('#js-dropdown-assignee'),
+ },
+ milestone: {
+ reference: null,
+ gl: 'DropdownNonUser',
+ extraArguments: [`${this.baseEndpoint}/milestones.json`, '%'],
+ element: this.container.querySelector('#js-dropdown-milestone'),
+ },
+ label: {
+ reference: null,
+ gl: 'DropdownNonUser',
+ extraArguments: [`${this.baseEndpoint}/labels.json`, '~'],
+ element: this.container.querySelector('#js-dropdown-label'),
+ },
+ hint: {
+ reference: null,
+ gl: 'DropdownHint',
+ element: this.container.querySelector('#js-dropdown-hint'),
+ },
+ };
+ }
- setupMapping() {
- this.mapping = {
- author: {
- reference: null,
- gl: 'DropdownUser',
- element: this.container.querySelector('#js-dropdown-author'),
- },
- assignee: {
- reference: null,
- gl: 'DropdownUser',
- element: this.container.querySelector('#js-dropdown-assignee'),
- },
- milestone: {
- reference: null,
- gl: 'DropdownNonUser',
- extraArguments: [`${this.baseEndpoint}/milestones.json`, '%'],
- element: this.container.querySelector('#js-dropdown-milestone'),
- },
- label: {
- reference: null,
- gl: 'DropdownNonUser',
- extraArguments: [`${this.baseEndpoint}/labels.json`, '~'],
- element: this.container.querySelector('#js-dropdown-label'),
- },
- hint: {
- reference: null,
- gl: 'DropdownHint',
- element: this.container.querySelector('#js-dropdown-hint'),
- },
- };
+ static addWordToInput(tokenName, tokenValue = '', clicked = false) {
+ const input = FilteredSearchContainer.container.querySelector('.filtered-search');
+
+ gl.FilteredSearchVisualTokens.addFilterVisualToken(tokenName, tokenValue);
+ input.value = '';
+
+ if (clicked) {
+ gl.FilteredSearchVisualTokens.moveInputToTheRight();
}
+ }
- static addWordToInput(tokenName, tokenValue = '', clicked = false) {
- const input = FilteredSearchContainer.container.querySelector('.filtered-search');
+ updateCurrentDropdownOffset() {
+ this.updateDropdownOffset(this.currentDropdown);
+ }
- gl.FilteredSearchVisualTokens.addFilterVisualToken(tokenName, tokenValue);
- input.value = '';
+ updateDropdownOffset(key) {
+ // Always align dropdown with the input field
+ let offset = this.filteredSearchInput.getBoundingClientRect().left - this.container.querySelector('.scroll-container').getBoundingClientRect().left;
- if (clicked) {
- gl.FilteredSearchVisualTokens.moveInputToTheRight();
- }
- }
+ const maxInputWidth = 240;
+ const currentDropdownWidth = this.mapping[key].element.clientWidth || maxInputWidth;
- updateCurrentDropdownOffset() {
- this.updateDropdownOffset(this.currentDropdown);
+ // Make sure offset never exceeds the input container
+ const offsetMaxWidth = this.container.querySelector('.scroll-container').clientWidth - currentDropdownWidth;
+ if (offsetMaxWidth < offset) {
+ offset = offsetMaxWidth;
}
- updateDropdownOffset(key) {
- // Always align dropdown with the input field
- let offset = this.filteredSearchInput.getBoundingClientRect().left - this.container.querySelector('.scroll-container').getBoundingClientRect().left;
+ this.mapping[key].reference.setOffset(offset);
+ }
- const maxInputWidth = 240;
- const currentDropdownWidth = this.mapping[key].element.clientWidth || maxInputWidth;
+ load(key, firstLoad = false) {
+ const mappingKey = this.mapping[key];
+ const glClass = mappingKey.gl;
+ const element = mappingKey.element;
+ let forceShowList = false;
- // Make sure offset never exceeds the input container
- const offsetMaxWidth = this.container.querySelector('.scroll-container').clientWidth - currentDropdownWidth;
- if (offsetMaxWidth < offset) {
- offset = offsetMaxWidth;
- }
+ if (!mappingKey.reference) {
+ const dl = this.droplab;
+ const defaultArguments = [null, dl, element, this.filteredSearchInput, key];
+ const glArguments = defaultArguments.concat(mappingKey.extraArguments || []);
- this.mapping[key].reference.setOffset(offset);
+ // Passing glArguments to `new gl[glClass](<arguments>)`
+ mappingKey.reference = new (Function.prototype.bind.apply(gl[glClass], glArguments))();
}
- load(key, firstLoad = false) {
- const mappingKey = this.mapping[key];
- const glClass = mappingKey.gl;
- const element = mappingKey.element;
- let forceShowList = false;
-
- if (!mappingKey.reference) {
- const dl = this.droplab;
- const defaultArguments = [null, dl, element, this.filteredSearchInput, key];
- const glArguments = defaultArguments.concat(mappingKey.extraArguments || []);
+ if (firstLoad) {
+ mappingKey.reference.init();
+ }
- // Passing glArguments to `new gl[glClass](<arguments>)`
- mappingKey.reference = new (Function.prototype.bind.apply(gl[glClass], glArguments))();
- }
+ if (this.currentDropdown === 'hint') {
+ // Force the dropdown to show if it was clicked from the hint dropdown
+ forceShowList = true;
+ }
- if (firstLoad) {
- mappingKey.reference.init();
- }
+ this.updateDropdownOffset(key);
+ mappingKey.reference.render(firstLoad, forceShowList);
- if (this.currentDropdown === 'hint') {
- // Force the dropdown to show if it was clicked from the hint dropdown
- forceShowList = true;
- }
+ this.currentDropdown = key;
+ }
- this.updateDropdownOffset(key);
- mappingKey.reference.render(firstLoad, forceShowList);
+ loadDropdown(dropdownName = '') {
+ let firstLoad = false;
- this.currentDropdown = key;
+ if (!this.droplab) {
+ firstLoad = true;
+ this.droplab = new DropLab();
}
- loadDropdown(dropdownName = '') {
- let firstLoad = false;
+ const match = this.filteredSearchTokenKeys.searchByKey(dropdownName.toLowerCase());
+ const shouldOpenFilterDropdown = match && this.currentDropdown !== match.key
+ && this.mapping[match.key];
+ const shouldOpenHintDropdown = !match && this.currentDropdown !== 'hint';
- if (!this.droplab) {
- firstLoad = true;
- this.droplab = new DropLab();
- }
+ if (shouldOpenFilterDropdown || shouldOpenHintDropdown) {
+ const key = match && match.key ? match.key : 'hint';
+ this.load(key, firstLoad);
+ }
+ }
- const match = this.filteredSearchTokenKeys.searchByKey(dropdownName.toLowerCase());
- const shouldOpenFilterDropdown = match && this.currentDropdown !== match.key
- && this.mapping[match.key];
- const shouldOpenHintDropdown = !match && this.currentDropdown !== 'hint';
+ setDropdown() {
+ const query = gl.DropdownUtils.getSearchQuery(true);
+ const { lastToken, searchToken } = this.tokenizer.processTokens(query);
- if (shouldOpenFilterDropdown || shouldOpenHintDropdown) {
- const key = match && match.key ? match.key : 'hint';
- this.load(key, firstLoad);
- }
+ if (this.currentDropdown) {
+ this.updateCurrentDropdownOffset();
}
- setDropdown() {
- const query = gl.DropdownUtils.getSearchQuery(true);
- const { lastToken, searchToken } = this.tokenizer.processTokens(query);
-
- if (this.currentDropdown) {
- this.updateCurrentDropdownOffset();
- }
-
- if (lastToken === searchToken && lastToken !== null) {
- // Token is not fully initialized yet because it has no value
- // Eg. token = 'label:'
-
- const split = lastToken.split(':');
- const dropdownName = split[0].split(' ').last();
- this.loadDropdown(split.length > 1 ? dropdownName : '');
- } else if (lastToken) {
- // Token has been initialized into an object because it has a value
- this.loadDropdown(lastToken.key);
- } else {
- this.loadDropdown('hint');
- }
+ if (lastToken === searchToken && lastToken !== null) {
+ // Token is not fully initialized yet because it has no value
+ // Eg. token = 'label:'
+
+ const split = lastToken.split(':');
+ const dropdownName = split[0].split(' ').last();
+ this.loadDropdown(split.length > 1 ? dropdownName : '');
+ } else if (lastToken) {
+ // Token has been initialized into an object because it has a value
+ this.loadDropdown(lastToken.key);
+ } else {
+ this.loadDropdown('hint');
}
+ }
- resetDropdowns() {
- if (!this.currentDropdown) {
- return;
- }
+ resetDropdowns() {
+ if (!this.currentDropdown) {
+ return;
+ }
- // Force current dropdown to hide
- this.mapping[this.currentDropdown].reference.hideDropdown();
+ // Force current dropdown to hide
+ this.mapping[this.currentDropdown].reference.hideDropdown();
- // Re-Load dropdown
- this.setDropdown();
+ // Re-Load dropdown
+ this.setDropdown();
- // Reset filters for current dropdown
- this.mapping[this.currentDropdown].reference.resetFilters();
+ // Reset filters for current dropdown
+ this.mapping[this.currentDropdown].reference.resetFilters();
- // Reposition dropdown so that it is aligned with cursor
- this.updateDropdownOffset(this.currentDropdown);
- }
+ // Reposition dropdown so that it is aligned with cursor
+ this.updateDropdownOffset(this.currentDropdown);
+ }
- destroyDroplab() {
- this.droplab.destroy();
- }
+ destroyDroplab() {
+ this.droplab.destroy();
}
+}
- window.gl = window.gl || {};
- gl.FilteredSearchDropdownManager = FilteredSearchDropdownManager;
-})();
+window.gl = window.gl || {};
+gl.FilteredSearchDropdownManager = FilteredSearchDropdownManager;
diff --git a/app/assets/javascripts/filtered_search/filtered_search_manager.js b/app/assets/javascripts/filtered_search/filtered_search_manager.js
index b93a8f1d322..a5eb33dd9de 100644
--- a/app/assets/javascripts/filtered_search/filtered_search_manager.js
+++ b/app/assets/javascripts/filtered_search/filtered_search_manager.js
@@ -6,489 +6,487 @@ import RecentSearchesStore from './stores/recent_searches_store';
import RecentSearchesService from './services/recent_searches_service';
import eventHub from './event_hub';
-(() => {
- class FilteredSearchManager {
- constructor(page) {
- this.container = FilteredSearchContainer.container;
- this.filteredSearchInput = this.container.querySelector('.filtered-search');
- this.filteredSearchInputForm = this.filteredSearchInput.form;
- this.clearSearchButton = this.container.querySelector('.clear-search');
- this.tokensContainer = this.container.querySelector('.tokens-container');
- this.filteredSearchTokenKeys = gl.FilteredSearchTokenKeys;
-
- this.recentSearchesStore = new RecentSearchesStore();
- let recentSearchesKey = 'issue-recent-searches';
- if (page === 'merge_requests') {
- recentSearchesKey = 'merge-request-recent-searches';
- }
- this.recentSearchesService = new RecentSearchesService(recentSearchesKey);
-
- // Fetch recent searches from localStorage
- this.fetchingRecentSearchesPromise = this.recentSearchesService.fetch()
- .catch(() => {
- // eslint-disable-next-line no-new
- new Flash('An error occured while parsing recent searches');
- // Gracefully fail to empty array
- return [];
- })
- .then((searches) => {
- // Put any searches that may have come in before
- // we fetched the saved searches ahead of the already saved ones
- const resultantSearches = this.recentSearchesStore.setRecentSearches(
- this.recentSearchesStore.state.recentSearches.concat(searches),
- );
- this.recentSearchesService.save(resultantSearches);
- });
-
- if (this.filteredSearchInput) {
- this.tokenizer = gl.FilteredSearchTokenizer;
- this.dropdownManager = new gl.FilteredSearchDropdownManager(this.filteredSearchInput.getAttribute('data-base-endpoint') || '', page);
-
- this.recentSearchesRoot = new RecentSearchesRoot(
- this.recentSearchesStore,
- this.recentSearchesService,
- document.querySelector('.js-filtered-search-history-dropdown'),
+class FilteredSearchManager {
+ constructor(page) {
+ this.container = FilteredSearchContainer.container;
+ this.filteredSearchInput = this.container.querySelector('.filtered-search');
+ this.filteredSearchInputForm = this.filteredSearchInput.form;
+ this.clearSearchButton = this.container.querySelector('.clear-search');
+ this.tokensContainer = this.container.querySelector('.tokens-container');
+ this.filteredSearchTokenKeys = gl.FilteredSearchTokenKeys;
+
+ this.recentSearchesStore = new RecentSearchesStore();
+ let recentSearchesKey = 'issue-recent-searches';
+ if (page === 'merge_requests') {
+ recentSearchesKey = 'merge-request-recent-searches';
+ }
+ this.recentSearchesService = new RecentSearchesService(recentSearchesKey);
+
+ // Fetch recent searches from localStorage
+ this.fetchingRecentSearchesPromise = this.recentSearchesService.fetch()
+ .catch(() => {
+ // eslint-disable-next-line no-new
+ new Flash('An error occured while parsing recent searches');
+ // Gracefully fail to empty array
+ return [];
+ })
+ .then((searches) => {
+ // Put any searches that may have come in before
+ // we fetched the saved searches ahead of the already saved ones
+ const resultantSearches = this.recentSearchesStore.setRecentSearches(
+ this.recentSearchesStore.state.recentSearches.concat(searches),
);
- this.recentSearchesRoot.init();
+ this.recentSearchesService.save(resultantSearches);
+ });
- this.bindEvents();
- this.loadSearchParamsFromURL();
- this.dropdownManager.setDropdown();
+ if (this.filteredSearchInput) {
+ this.tokenizer = gl.FilteredSearchTokenizer;
+ this.dropdownManager = new gl.FilteredSearchDropdownManager(this.filteredSearchInput.getAttribute('data-base-endpoint') || '', page);
- this.cleanupWrapper = this.cleanup.bind(this);
- document.addEventListener('beforeunload', this.cleanupWrapper);
- }
- }
+ this.recentSearchesRoot = new RecentSearchesRoot(
+ this.recentSearchesStore,
+ this.recentSearchesService,
+ document.querySelector('.js-filtered-search-history-dropdown'),
+ );
+ this.recentSearchesRoot.init();
- cleanup() {
- this.unbindEvents();
- document.removeEventListener('beforeunload', this.cleanupWrapper);
+ this.bindEvents();
+ this.loadSearchParamsFromURL();
+ this.dropdownManager.setDropdown();
- if (this.recentSearchesRoot) {
- this.recentSearchesRoot.destroy();
- }
+ this.cleanupWrapper = this.cleanup.bind(this);
+ document.addEventListener('beforeunload', this.cleanupWrapper);
}
+ }
- bindEvents() {
- this.handleFormSubmit = this.handleFormSubmit.bind(this);
- this.setDropdownWrapper = this.dropdownManager.setDropdown.bind(this.dropdownManager);
- this.toggleClearSearchButtonWrapper = this.toggleClearSearchButton.bind(this);
- this.handleInputPlaceholderWrapper = this.handleInputPlaceholder.bind(this);
- this.handleInputVisualTokenWrapper = this.handleInputVisualToken.bind(this);
- this.checkForEnterWrapper = this.checkForEnter.bind(this);
- this.onClearSearchWrapper = this.onClearSearch.bind(this);
- this.checkForBackspaceWrapper = this.checkForBackspace.bind(this);
- this.removeSelectedTokenWrapper = this.removeSelectedToken.bind(this);
- this.unselectEditTokensWrapper = this.unselectEditTokens.bind(this);
- this.editTokenWrapper = this.editToken.bind(this);
- this.tokenChange = this.tokenChange.bind(this);
- this.addInputContainerFocusWrapper = this.addInputContainerFocus.bind(this);
- this.removeInputContainerFocusWrapper = this.removeInputContainerFocus.bind(this);
- this.onrecentSearchesItemSelectedWrapper = this.onrecentSearchesItemSelected.bind(this);
-
- this.filteredSearchInputForm.addEventListener('submit', this.handleFormSubmit);
- this.filteredSearchInput.addEventListener('input', this.setDropdownWrapper);
- this.filteredSearchInput.addEventListener('input', this.toggleClearSearchButtonWrapper);
- this.filteredSearchInput.addEventListener('input', this.handleInputPlaceholderWrapper);
- this.filteredSearchInput.addEventListener('input', this.handleInputVisualTokenWrapper);
- this.filteredSearchInput.addEventListener('keydown', this.checkForEnterWrapper);
- this.filteredSearchInput.addEventListener('keyup', this.checkForBackspaceWrapper);
- this.filteredSearchInput.addEventListener('click', this.tokenChange);
- this.filteredSearchInput.addEventListener('keyup', this.tokenChange);
- this.filteredSearchInput.addEventListener('focus', this.addInputContainerFocusWrapper);
- this.tokensContainer.addEventListener('click', FilteredSearchManager.selectToken);
- this.tokensContainer.addEventListener('dblclick', this.editTokenWrapper);
- this.clearSearchButton.addEventListener('click', this.onClearSearchWrapper);
- document.addEventListener('click', gl.FilteredSearchVisualTokens.unselectTokens);
- document.addEventListener('click', this.unselectEditTokensWrapper);
- document.addEventListener('click', this.removeInputContainerFocusWrapper);
- document.addEventListener('keydown', this.removeSelectedTokenWrapper);
- eventHub.$on('recentSearchesItemSelected', this.onrecentSearchesItemSelectedWrapper);
- }
+ cleanup() {
+ this.unbindEvents();
+ document.removeEventListener('beforeunload', this.cleanupWrapper);
- unbindEvents() {
- this.filteredSearchInputForm.removeEventListener('submit', this.handleFormSubmit);
- this.filteredSearchInput.removeEventListener('input', this.setDropdownWrapper);
- this.filteredSearchInput.removeEventListener('input', this.toggleClearSearchButtonWrapper);
- this.filteredSearchInput.removeEventListener('input', this.handleInputPlaceholderWrapper);
- this.filteredSearchInput.removeEventListener('input', this.handleInputVisualTokenWrapper);
- this.filteredSearchInput.removeEventListener('keydown', this.checkForEnterWrapper);
- this.filteredSearchInput.removeEventListener('keyup', this.checkForBackspaceWrapper);
- this.filteredSearchInput.removeEventListener('click', this.tokenChange);
- this.filteredSearchInput.removeEventListener('keyup', this.tokenChange);
- this.filteredSearchInput.removeEventListener('focus', this.addInputContainerFocusWrapper);
- this.tokensContainer.removeEventListener('click', FilteredSearchManager.selectToken);
- this.tokensContainer.removeEventListener('dblclick', this.editTokenWrapper);
- this.clearSearchButton.removeEventListener('click', this.onClearSearchWrapper);
- document.removeEventListener('click', gl.FilteredSearchVisualTokens.unselectTokens);
- document.removeEventListener('click', this.unselectEditTokensWrapper);
- document.removeEventListener('click', this.removeInputContainerFocusWrapper);
- document.removeEventListener('keydown', this.removeSelectedTokenWrapper);
- eventHub.$off('recentSearchesItemSelected', this.onrecentSearchesItemSelectedWrapper);
+ if (this.recentSearchesRoot) {
+ this.recentSearchesRoot.destroy();
}
+ }
- checkForBackspace(e) {
- // 8 = Backspace Key
- // 46 = Delete Key
- if (e.keyCode === 8 || e.keyCode === 46) {
- const { lastVisualToken } = gl.FilteredSearchVisualTokens.getLastVisualTokenBeforeInput();
+ bindEvents() {
+ this.handleFormSubmit = this.handleFormSubmit.bind(this);
+ this.setDropdownWrapper = this.dropdownManager.setDropdown.bind(this.dropdownManager);
+ this.toggleClearSearchButtonWrapper = this.toggleClearSearchButton.bind(this);
+ this.handleInputPlaceholderWrapper = this.handleInputPlaceholder.bind(this);
+ this.handleInputVisualTokenWrapper = this.handleInputVisualToken.bind(this);
+ this.checkForEnterWrapper = this.checkForEnter.bind(this);
+ this.onClearSearchWrapper = this.onClearSearch.bind(this);
+ this.checkForBackspaceWrapper = this.checkForBackspace.bind(this);
+ this.removeSelectedTokenWrapper = this.removeSelectedToken.bind(this);
+ this.unselectEditTokensWrapper = this.unselectEditTokens.bind(this);
+ this.editTokenWrapper = this.editToken.bind(this);
+ this.tokenChange = this.tokenChange.bind(this);
+ this.addInputContainerFocusWrapper = this.addInputContainerFocus.bind(this);
+ this.removeInputContainerFocusWrapper = this.removeInputContainerFocus.bind(this);
+ this.onrecentSearchesItemSelectedWrapper = this.onrecentSearchesItemSelected.bind(this);
+
+ this.filteredSearchInputForm.addEventListener('submit', this.handleFormSubmit);
+ this.filteredSearchInput.addEventListener('input', this.setDropdownWrapper);
+ this.filteredSearchInput.addEventListener('input', this.toggleClearSearchButtonWrapper);
+ this.filteredSearchInput.addEventListener('input', this.handleInputPlaceholderWrapper);
+ this.filteredSearchInput.addEventListener('input', this.handleInputVisualTokenWrapper);
+ this.filteredSearchInput.addEventListener('keydown', this.checkForEnterWrapper);
+ this.filteredSearchInput.addEventListener('keyup', this.checkForBackspaceWrapper);
+ this.filteredSearchInput.addEventListener('click', this.tokenChange);
+ this.filteredSearchInput.addEventListener('keyup', this.tokenChange);
+ this.filteredSearchInput.addEventListener('focus', this.addInputContainerFocusWrapper);
+ this.tokensContainer.addEventListener('click', FilteredSearchManager.selectToken);
+ this.tokensContainer.addEventListener('dblclick', this.editTokenWrapper);
+ this.clearSearchButton.addEventListener('click', this.onClearSearchWrapper);
+ document.addEventListener('click', gl.FilteredSearchVisualTokens.unselectTokens);
+ document.addEventListener('click', this.unselectEditTokensWrapper);
+ document.addEventListener('click', this.removeInputContainerFocusWrapper);
+ document.addEventListener('keydown', this.removeSelectedTokenWrapper);
+ eventHub.$on('recentSearchesItemSelected', this.onrecentSearchesItemSelectedWrapper);
+ }
- if (this.filteredSearchInput.value === '' && lastVisualToken) {
- this.filteredSearchInput.value = gl.FilteredSearchVisualTokens.getLastTokenPartial();
- gl.FilteredSearchVisualTokens.removeLastTokenPartial();
- }
+ unbindEvents() {
+ this.filteredSearchInputForm.removeEventListener('submit', this.handleFormSubmit);
+ this.filteredSearchInput.removeEventListener('input', this.setDropdownWrapper);
+ this.filteredSearchInput.removeEventListener('input', this.toggleClearSearchButtonWrapper);
+ this.filteredSearchInput.removeEventListener('input', this.handleInputPlaceholderWrapper);
+ this.filteredSearchInput.removeEventListener('input', this.handleInputVisualTokenWrapper);
+ this.filteredSearchInput.removeEventListener('keydown', this.checkForEnterWrapper);
+ this.filteredSearchInput.removeEventListener('keyup', this.checkForBackspaceWrapper);
+ this.filteredSearchInput.removeEventListener('click', this.tokenChange);
+ this.filteredSearchInput.removeEventListener('keyup', this.tokenChange);
+ this.filteredSearchInput.removeEventListener('focus', this.addInputContainerFocusWrapper);
+ this.tokensContainer.removeEventListener('click', FilteredSearchManager.selectToken);
+ this.tokensContainer.removeEventListener('dblclick', this.editTokenWrapper);
+ this.clearSearchButton.removeEventListener('click', this.onClearSearchWrapper);
+ document.removeEventListener('click', gl.FilteredSearchVisualTokens.unselectTokens);
+ document.removeEventListener('click', this.unselectEditTokensWrapper);
+ document.removeEventListener('click', this.removeInputContainerFocusWrapper);
+ document.removeEventListener('keydown', this.removeSelectedTokenWrapper);
+ eventHub.$off('recentSearchesItemSelected', this.onrecentSearchesItemSelectedWrapper);
+ }
+
+ checkForBackspace(e) {
+ // 8 = Backspace Key
+ // 46 = Delete Key
+ if (e.keyCode === 8 || e.keyCode === 46) {
+ const { lastVisualToken } = gl.FilteredSearchVisualTokens.getLastVisualTokenBeforeInput();
- // Reposition dropdown so that it is aligned with cursor
- this.dropdownManager.updateCurrentDropdownOffset();
+ if (this.filteredSearchInput.value === '' && lastVisualToken) {
+ this.filteredSearchInput.value = gl.FilteredSearchVisualTokens.getLastTokenPartial();
+ gl.FilteredSearchVisualTokens.removeLastTokenPartial();
}
- }
- checkForEnter(e) {
- if (e.keyCode === 38 || e.keyCode === 40) {
- const selectionStart = this.filteredSearchInput.selectionStart;
+ // Reposition dropdown so that it is aligned with cursor
+ this.dropdownManager.updateCurrentDropdownOffset();
+ }
+ }
- e.preventDefault();
- this.filteredSearchInput.setSelectionRange(selectionStart, selectionStart);
- }
+ checkForEnter(e) {
+ if (e.keyCode === 38 || e.keyCode === 40) {
+ const selectionStart = this.filteredSearchInput.selectionStart;
- if (e.keyCode === 13) {
- const dropdown = this.dropdownManager.mapping[this.dropdownManager.currentDropdown];
- const dropdownEl = dropdown.element;
- const activeElements = dropdownEl.querySelectorAll('.droplab-item-active');
+ e.preventDefault();
+ this.filteredSearchInput.setSelectionRange(selectionStart, selectionStart);
+ }
- e.preventDefault();
+ if (e.keyCode === 13) {
+ const dropdown = this.dropdownManager.mapping[this.dropdownManager.currentDropdown];
+ const dropdownEl = dropdown.element;
+ const activeElements = dropdownEl.querySelectorAll('.droplab-item-active');
- if (!activeElements.length) {
- if (this.isHandledAsync) {
- e.stopImmediatePropagation();
+ e.preventDefault();
- this.filteredSearchInput.blur();
- this.dropdownManager.resetDropdowns();
- } else {
- // Prevent droplab from opening dropdown
- this.dropdownManager.destroyDroplab();
- }
+ if (!activeElements.length) {
+ if (this.isHandledAsync) {
+ e.stopImmediatePropagation();
- this.search();
+ this.filteredSearchInput.blur();
+ this.dropdownManager.resetDropdowns();
+ } else {
+ // Prevent droplab from opening dropdown
+ this.dropdownManager.destroyDroplab();
}
+
+ this.search();
}
}
+ }
- addInputContainerFocus() {
- const inputContainer = this.filteredSearchInput.closest('.filtered-search-box');
+ addInputContainerFocus() {
+ const inputContainer = this.filteredSearchInput.closest('.filtered-search-box');
- if (inputContainer) {
- inputContainer.classList.add('focus');
- }
+ if (inputContainer) {
+ inputContainer.classList.add('focus');
}
+ }
- removeInputContainerFocus(e) {
- const inputContainer = this.filteredSearchInput.closest('.filtered-search-box');
- const isElementInFilteredSearch = inputContainer && inputContainer.contains(e.target);
- const isElementInDynamicFilterDropdown = e.target.closest('.filter-dropdown') !== null;
- const isElementInStaticFilterDropdown = e.target.closest('ul[data-dropdown]') !== null;
+ removeInputContainerFocus(e) {
+ const inputContainer = this.filteredSearchInput.closest('.filtered-search-box');
+ const isElementInFilteredSearch = inputContainer && inputContainer.contains(e.target);
+ const isElementInDynamicFilterDropdown = e.target.closest('.filter-dropdown') !== null;
+ const isElementInStaticFilterDropdown = e.target.closest('ul[data-dropdown]') !== null;
- if (!isElementInFilteredSearch && !isElementInDynamicFilterDropdown &&
- !isElementInStaticFilterDropdown && inputContainer) {
- inputContainer.classList.remove('focus');
- }
+ if (!isElementInFilteredSearch && !isElementInDynamicFilterDropdown &&
+ !isElementInStaticFilterDropdown && inputContainer) {
+ inputContainer.classList.remove('focus');
}
+ }
- static selectToken(e) {
- const button = e.target.closest('.selectable');
+ static selectToken(e) {
+ const button = e.target.closest('.selectable');
- if (button) {
- e.preventDefault();
- e.stopPropagation();
- gl.FilteredSearchVisualTokens.selectToken(button);
- }
+ if (button) {
+ e.preventDefault();
+ e.stopPropagation();
+ gl.FilteredSearchVisualTokens.selectToken(button);
}
+ }
- unselectEditTokens(e) {
- const inputContainer = this.container.querySelector('.filtered-search-box');
- const isElementInFilteredSearch = inputContainer && inputContainer.contains(e.target);
- const isElementInFilterDropdown = e.target.closest('.filter-dropdown') !== null;
- const isElementTokensContainer = e.target.classList.contains('tokens-container');
+ unselectEditTokens(e) {
+ const inputContainer = this.container.querySelector('.filtered-search-box');
+ const isElementInFilteredSearch = inputContainer && inputContainer.contains(e.target);
+ const isElementInFilterDropdown = e.target.closest('.filter-dropdown') !== null;
+ const isElementTokensContainer = e.target.classList.contains('tokens-container');
- if ((!isElementInFilteredSearch && !isElementInFilterDropdown) || isElementTokensContainer) {
- gl.FilteredSearchVisualTokens.moveInputToTheRight();
- this.dropdownManager.resetDropdowns();
- }
+ if ((!isElementInFilteredSearch && !isElementInFilterDropdown) || isElementTokensContainer) {
+ gl.FilteredSearchVisualTokens.moveInputToTheRight();
+ this.dropdownManager.resetDropdowns();
}
+ }
- editToken(e) {
- const token = e.target.closest('.js-visual-token');
+ editToken(e) {
+ const token = e.target.closest('.js-visual-token');
- if (token) {
- gl.FilteredSearchVisualTokens.editToken(token);
- this.tokenChange();
- }
+ if (token) {
+ gl.FilteredSearchVisualTokens.editToken(token);
+ this.tokenChange();
}
+ }
- toggleClearSearchButton() {
- const query = gl.DropdownUtils.getSearchQuery();
- const hidden = 'hidden';
- const hasHidden = this.clearSearchButton.classList.contains(hidden);
+ toggleClearSearchButton() {
+ const query = gl.DropdownUtils.getSearchQuery();
+ const hidden = 'hidden';
+ const hasHidden = this.clearSearchButton.classList.contains(hidden);
- if (query.length === 0 && !hasHidden) {
- this.clearSearchButton.classList.add(hidden);
- } else if (query.length && hasHidden) {
- this.clearSearchButton.classList.remove(hidden);
- }
+ if (query.length === 0 && !hasHidden) {
+ this.clearSearchButton.classList.add(hidden);
+ } else if (query.length && hasHidden) {
+ this.clearSearchButton.classList.remove(hidden);
}
+ }
- handleInputPlaceholder() {
- const query = gl.DropdownUtils.getSearchQuery();
- const placeholder = 'Search or filter results...';
- const currentPlaceholder = this.filteredSearchInput.placeholder;
+ handleInputPlaceholder() {
+ const query = gl.DropdownUtils.getSearchQuery();
+ const placeholder = 'Search or filter results...';
+ const currentPlaceholder = this.filteredSearchInput.placeholder;
- if (query.length === 0 && currentPlaceholder !== placeholder) {
- this.filteredSearchInput.placeholder = placeholder;
- } else if (query.length > 0 && currentPlaceholder !== '') {
- this.filteredSearchInput.placeholder = '';
- }
+ if (query.length === 0 && currentPlaceholder !== placeholder) {
+ this.filteredSearchInput.placeholder = placeholder;
+ } else if (query.length > 0 && currentPlaceholder !== '') {
+ this.filteredSearchInput.placeholder = '';
}
+ }
- removeSelectedToken(e) {
- // 8 = Backspace Key
- // 46 = Delete Key
- if (e.keyCode === 8 || e.keyCode === 46) {
- gl.FilteredSearchVisualTokens.removeSelectedToken();
- this.handleInputPlaceholder();
- this.toggleClearSearchButton();
- }
+ removeSelectedToken(e) {
+ // 8 = Backspace Key
+ // 46 = Delete Key
+ if (e.keyCode === 8 || e.keyCode === 46) {
+ gl.FilteredSearchVisualTokens.removeSelectedToken();
+ this.handleInputPlaceholder();
+ this.toggleClearSearchButton();
}
+ }
- onClearSearch(e) {
- e.preventDefault();
- this.clearSearch();
- }
+ onClearSearch(e) {
+ e.preventDefault();
+ this.clearSearch();
+ }
- clearSearch() {
- this.filteredSearchInput.value = '';
+ clearSearch() {
+ this.filteredSearchInput.value = '';
- const removeElements = [];
+ const removeElements = [];
- [].forEach.call(this.tokensContainer.children, (t) => {
- if (t.classList.contains('js-visual-token')) {
- removeElements.push(t);
- }
- });
+ [].forEach.call(this.tokensContainer.children, (t) => {
+ if (t.classList.contains('js-visual-token')) {
+ removeElements.push(t);
+ }
+ });
- removeElements.forEach((el) => {
- el.parentElement.removeChild(el);
- });
+ removeElements.forEach((el) => {
+ el.parentElement.removeChild(el);
+ });
- this.clearSearchButton.classList.add('hidden');
- this.handleInputPlaceholder();
+ this.clearSearchButton.classList.add('hidden');
+ this.handleInputPlaceholder();
- this.dropdownManager.resetDropdowns();
+ this.dropdownManager.resetDropdowns();
- if (this.isHandledAsync) {
- this.search();
- }
+ if (this.isHandledAsync) {
+ this.search();
}
+ }
- handleInputVisualToken() {
- const input = this.filteredSearchInput;
- const { tokens, searchToken }
- = gl.FilteredSearchTokenizer.processTokens(input.value);
- const { isLastVisualTokenValid }
- = gl.FilteredSearchVisualTokens.getLastVisualTokenBeforeInput();
-
- if (isLastVisualTokenValid) {
- tokens.forEach((t) => {
- input.value = input.value.replace(`${t.key}:${t.symbol}${t.value}`, '');
- gl.FilteredSearchVisualTokens.addFilterVisualToken(t.key, `${t.symbol}${t.value}`);
- });
-
- const fragments = searchToken.split(':');
- if (fragments.length > 1) {
- const inputValues = fragments[0].split(' ');
- const tokenKey = inputValues.last();
-
- if (inputValues.length > 1) {
- inputValues.pop();
- const searchTerms = inputValues.join(' ');
-
- input.value = input.value.replace(searchTerms, '');
- gl.FilteredSearchVisualTokens.addSearchVisualToken(searchTerms);
- }
+ handleInputVisualToken() {
+ const input = this.filteredSearchInput;
+ const { tokens, searchToken }
+ = gl.FilteredSearchTokenizer.processTokens(input.value);
+ const { isLastVisualTokenValid }
+ = gl.FilteredSearchVisualTokens.getLastVisualTokenBeforeInput();
+
+ if (isLastVisualTokenValid) {
+ tokens.forEach((t) => {
+ input.value = input.value.replace(`${t.key}:${t.symbol}${t.value}`, '');
+ gl.FilteredSearchVisualTokens.addFilterVisualToken(t.key, `${t.symbol}${t.value}`);
+ });
- gl.FilteredSearchVisualTokens.addFilterVisualToken(tokenKey);
- input.value = input.value.replace(`${tokenKey}:`, '');
- }
- } else {
- // Keep listening to token until we determine that the user is done typing the token value
- const valueCompletedRegex = /([~%@]{0,1}".+")|([~%@]{0,1}'.+')|^((?![~%@]')(?![~%@]")(?!')(?!")).*/g;
+ const fragments = searchToken.split(':');
+ if (fragments.length > 1) {
+ const inputValues = fragments[0].split(' ');
+ const tokenKey = inputValues.last();
- if (searchToken.match(valueCompletedRegex) && input.value[input.value.length - 1] === ' ') {
- gl.FilteredSearchVisualTokens.addFilterVisualToken(searchToken);
+ if (inputValues.length > 1) {
+ inputValues.pop();
+ const searchTerms = inputValues.join(' ');
- // Trim the last space as seen in the if statement above
- input.value = input.value.replace(searchToken, '').trim();
+ input.value = input.value.replace(searchTerms, '');
+ gl.FilteredSearchVisualTokens.addSearchVisualToken(searchTerms);
}
+
+ gl.FilteredSearchVisualTokens.addFilterVisualToken(tokenKey);
+ input.value = input.value.replace(`${tokenKey}:`, '');
}
- }
+ } else {
+ // Keep listening to token until we determine that the user is done typing the token value
+ const valueCompletedRegex = /([~%@]{0,1}".+")|([~%@]{0,1}'.+')|^((?![~%@]')(?![~%@]")(?!')(?!")).*/g;
- handleFormSubmit(e) {
- e.preventDefault();
- this.search();
- }
+ if (searchToken.match(valueCompletedRegex) && input.value[input.value.length - 1] === ' ') {
+ gl.FilteredSearchVisualTokens.addFilterVisualToken(searchToken);
- saveCurrentSearchQuery() {
- // Don't save before we have fetched the already saved searches
- this.fetchingRecentSearchesPromise.then(() => {
- const searchQuery = gl.DropdownUtils.getSearchQuery();
- if (searchQuery.length > 0) {
- const resultantSearches = this.recentSearchesStore.addRecentSearch(searchQuery);
- this.recentSearchesService.save(resultantSearches);
- }
- });
+ // Trim the last space as seen in the if statement above
+ input.value = input.value.replace(searchToken, '').trim();
+ }
}
+ }
- loadSearchParamsFromURL() {
- const params = gl.utils.getUrlParamsArray();
- const usernameParams = this.getUsernameParams();
- let hasFilteredSearch = false;
+ handleFormSubmit(e) {
+ e.preventDefault();
+ this.search();
+ }
- params.forEach((p) => {
- const split = p.split('=');
- const keyParam = decodeURIComponent(split[0]);
- const value = split[1];
+ saveCurrentSearchQuery() {
+ // Don't save before we have fetched the already saved searches
+ this.fetchingRecentSearchesPromise.then(() => {
+ const searchQuery = gl.DropdownUtils.getSearchQuery();
+ if (searchQuery.length > 0) {
+ const resultantSearches = this.recentSearchesStore.addRecentSearch(searchQuery);
+ this.recentSearchesService.save(resultantSearches);
+ }
+ });
+ }
- // Check if it matches edge conditions listed in this.filteredSearchTokenKeys
- const condition = this.filteredSearchTokenKeys.searchByConditionUrl(p);
+ loadSearchParamsFromURL() {
+ const params = gl.utils.getUrlParamsArray();
+ const usernameParams = this.getUsernameParams();
+ let hasFilteredSearch = false;
- if (condition) {
- hasFilteredSearch = true;
- gl.FilteredSearchVisualTokens.addFilterVisualToken(condition.tokenKey, condition.value);
- } else {
- // Sanitize value since URL converts spaces into +
- // Replace before decode so that we know what was originally + versus the encoded +
- const sanitizedValue = value ? decodeURIComponent(value.replace(/\+/g, ' ')) : value;
- const match = this.filteredSearchTokenKeys.searchByKeyParam(keyParam);
-
- if (match) {
- const indexOf = keyParam.indexOf('_');
- const sanitizedKey = indexOf !== -1 ? keyParam.slice(0, keyParam.indexOf('_')) : keyParam;
- const symbol = match.symbol;
- let quotationsToUse = '';
-
- if (sanitizedValue.indexOf(' ') !== -1) {
- // Prefer ", but use ' if required
- quotationsToUse = sanitizedValue.indexOf('"') === -1 ? '"' : '\'';
- }
+ params.forEach((p) => {
+ const split = p.split('=');
+ const keyParam = decodeURIComponent(split[0]);
+ const value = split[1];
+
+ // Check if it matches edge conditions listed in this.filteredSearchTokenKeys
+ const condition = this.filteredSearchTokenKeys.searchByConditionUrl(p);
+ if (condition) {
+ hasFilteredSearch = true;
+ gl.FilteredSearchVisualTokens.addFilterVisualToken(condition.tokenKey, condition.value);
+ } else {
+ // Sanitize value since URL converts spaces into +
+ // Replace before decode so that we know what was originally + versus the encoded +
+ const sanitizedValue = value ? decodeURIComponent(value.replace(/\+/g, ' ')) : value;
+ const match = this.filteredSearchTokenKeys.searchByKeyParam(keyParam);
+
+ if (match) {
+ const indexOf = keyParam.indexOf('_');
+ const sanitizedKey = indexOf !== -1 ? keyParam.slice(0, keyParam.indexOf('_')) : keyParam;
+ const symbol = match.symbol;
+ let quotationsToUse = '';
+
+ if (sanitizedValue.indexOf(' ') !== -1) {
+ // Prefer ", but use ' if required
+ quotationsToUse = sanitizedValue.indexOf('"') === -1 ? '"' : '\'';
+ }
+
+ hasFilteredSearch = true;
+ gl.FilteredSearchVisualTokens.addFilterVisualToken(sanitizedKey, `${symbol}${quotationsToUse}${sanitizedValue}${quotationsToUse}`);
+ } else if (!match && keyParam === 'assignee_id') {
+ const id = parseInt(value, 10);
+ if (usernameParams[id]) {
hasFilteredSearch = true;
- gl.FilteredSearchVisualTokens.addFilterVisualToken(sanitizedKey, `${symbol}${quotationsToUse}${sanitizedValue}${quotationsToUse}`);
- } else if (!match && keyParam === 'assignee_id') {
- const id = parseInt(value, 10);
- if (usernameParams[id]) {
- hasFilteredSearch = true;
- gl.FilteredSearchVisualTokens.addFilterVisualToken('assignee', `@${usernameParams[id]}`);
- }
- } else if (!match && keyParam === 'author_id') {
- const id = parseInt(value, 10);
- if (usernameParams[id]) {
- hasFilteredSearch = true;
- gl.FilteredSearchVisualTokens.addFilterVisualToken('author', `@${usernameParams[id]}`);
- }
- } else if (!match && keyParam === 'search') {
+ gl.FilteredSearchVisualTokens.addFilterVisualToken('assignee', `@${usernameParams[id]}`);
+ }
+ } else if (!match && keyParam === 'author_id') {
+ const id = parseInt(value, 10);
+ if (usernameParams[id]) {
hasFilteredSearch = true;
- this.filteredSearchInput.value = sanitizedValue;
+ gl.FilteredSearchVisualTokens.addFilterVisualToken('author', `@${usernameParams[id]}`);
}
+ } else if (!match && keyParam === 'search') {
+ hasFilteredSearch = true;
+ this.filteredSearchInput.value = sanitizedValue;
}
- });
+ }
+ });
- this.saveCurrentSearchQuery();
+ this.saveCurrentSearchQuery();
- if (hasFilteredSearch) {
- this.clearSearchButton.classList.remove('hidden');
- this.handleInputPlaceholder();
- }
+ if (hasFilteredSearch) {
+ this.clearSearchButton.classList.remove('hidden');
+ this.handleInputPlaceholder();
}
+ }
- search() {
- const paths = [];
- const searchQuery = gl.DropdownUtils.getSearchQuery();
-
- this.saveCurrentSearchQuery();
+ search() {
+ const paths = [];
+ const searchQuery = gl.DropdownUtils.getSearchQuery();
- const { tokens, searchToken }
- = this.tokenizer.processTokens(searchQuery);
- const currentState = gl.utils.getParameterByName('state') || 'opened';
- paths.push(`state=${currentState}`);
+ this.saveCurrentSearchQuery();
- tokens.forEach((token) => {
- const condition = this.filteredSearchTokenKeys
- .searchByConditionKeyValue(token.key, token.value.toLowerCase());
- const { param } = this.filteredSearchTokenKeys.searchByKey(token.key) || {};
- const keyParam = param ? `${token.key}_${param}` : token.key;
- let tokenPath = '';
+ const { tokens, searchToken }
+ = this.tokenizer.processTokens(searchQuery);
+ const currentState = gl.utils.getParameterByName('state') || 'opened';
+ paths.push(`state=${currentState}`);
- if (condition) {
- tokenPath = condition.url;
- } else {
- let tokenValue = token.value;
+ tokens.forEach((token) => {
+ const condition = this.filteredSearchTokenKeys
+ .searchByConditionKeyValue(token.key, token.value.toLowerCase());
+ const { param } = this.filteredSearchTokenKeys.searchByKey(token.key) || {};
+ const keyParam = param ? `${token.key}_${param}` : token.key;
+ let tokenPath = '';
- if ((tokenValue[0] === '\'' && tokenValue[tokenValue.length - 1] === '\'') ||
- (tokenValue[0] === '"' && tokenValue[tokenValue.length - 1] === '"')) {
- tokenValue = tokenValue.slice(1, tokenValue.length - 1);
- }
+ if (condition) {
+ tokenPath = condition.url;
+ } else {
+ let tokenValue = token.value;
- tokenPath = `${keyParam}=${encodeURIComponent(tokenValue)}`;
+ if ((tokenValue[0] === '\'' && tokenValue[tokenValue.length - 1] === '\'') ||
+ (tokenValue[0] === '"' && tokenValue[tokenValue.length - 1] === '"')) {
+ tokenValue = tokenValue.slice(1, tokenValue.length - 1);
}
- paths.push(tokenPath);
- });
-
- if (searchToken) {
- const sanitized = searchToken.split(' ').map(t => encodeURIComponent(t)).join('+');
- paths.push(`search=${sanitized}`);
+ tokenPath = `${keyParam}=${encodeURIComponent(tokenValue)}`;
}
- const parameterizedUrl = `?scope=all&utf8=%E2%9C%93&${paths.join('&')}`;
+ paths.push(tokenPath);
+ });
- if (this.updateObject) {
- this.updateObject(parameterizedUrl);
- } else {
- gl.utils.visitUrl(parameterizedUrl);
- }
+ if (searchToken) {
+ const sanitized = searchToken.split(' ').map(t => encodeURIComponent(t)).join('+');
+ paths.push(`search=${sanitized}`);
}
- getUsernameParams() {
- const usernamesById = {};
- try {
- const attribute = this.filteredSearchInput.getAttribute('data-username-params');
- JSON.parse(attribute).forEach((user) => {
- usernamesById[user.id] = user.username;
- });
- } catch (e) {
- // do nothing
- }
- return usernamesById;
+ const parameterizedUrl = `?scope=all&utf8=%E2%9C%93&${paths.join('&')}`;
+
+ if (this.updateObject) {
+ this.updateObject(parameterizedUrl);
+ } else {
+ gl.utils.visitUrl(parameterizedUrl);
}
+ }
- tokenChange() {
- const dropdown = this.dropdownManager.mapping[this.dropdownManager.currentDropdown];
+ getUsernameParams() {
+ const usernamesById = {};
+ try {
+ const attribute = this.filteredSearchInput.getAttribute('data-username-params');
+ JSON.parse(attribute).forEach((user) => {
+ usernamesById[user.id] = user.username;
+ });
+ } catch (e) {
+ // do nothing
+ }
+ return usernamesById;
+ }
- if (dropdown) {
- const currentDropdownRef = dropdown.reference;
+ tokenChange() {
+ const dropdown = this.dropdownManager.mapping[this.dropdownManager.currentDropdown];
- this.setDropdownWrapper();
- currentDropdownRef.dispatchInputEvent();
- }
- }
+ if (dropdown) {
+ const currentDropdownRef = dropdown.reference;
- onrecentSearchesItemSelected(text) {
- this.clearSearch();
- this.filteredSearchInput.value = text;
- this.filteredSearchInput.dispatchEvent(new CustomEvent('input'));
- this.search();
+ this.setDropdownWrapper();
+ currentDropdownRef.dispatchInputEvent();
}
}
- window.gl = window.gl || {};
- gl.FilteredSearchManager = FilteredSearchManager;
-})();
+ onrecentSearchesItemSelected(text) {
+ this.clearSearch();
+ this.filteredSearchInput.value = text;
+ this.filteredSearchInput.dispatchEvent(new CustomEvent('input'));
+ this.search();
+ }
+}
+
+window.gl = window.gl || {};
+gl.FilteredSearchManager = FilteredSearchManager;
diff --git a/app/assets/javascripts/filtered_search/filtered_search_token_keys.js b/app/assets/javascripts/filtered_search/filtered_search_token_keys.js
index 6d5df86f2a5..1abad9d1b73 100644
--- a/app/assets/javascripts/filtered_search/filtered_search_token_keys.js
+++ b/app/assets/javascripts/filtered_search/filtered_search_token_keys.js
@@ -1,100 +1,98 @@
-(() => {
- const tokenKeys = [{
- key: 'author',
- type: 'string',
- param: 'username',
- symbol: '@',
- }, {
- key: 'assignee',
- type: 'string',
- param: 'username',
- symbol: '@',
- }, {
- key: 'milestone',
- type: 'string',
- param: 'title',
- symbol: '%',
- }, {
- key: 'label',
- type: 'array',
- param: 'name[]',
- symbol: '~',
- }];
+const tokenKeys = [{
+ key: 'author',
+ type: 'string',
+ param: 'username',
+ symbol: '@',
+}, {
+ key: 'assignee',
+ type: 'string',
+ param: 'username',
+ symbol: '@',
+}, {
+ key: 'milestone',
+ type: 'string',
+ param: 'title',
+ symbol: '%',
+}, {
+ key: 'label',
+ type: 'array',
+ param: 'name[]',
+ symbol: '~',
+}];
- const alternativeTokenKeys = [{
- key: 'label',
- type: 'string',
- param: 'name',
- symbol: '~',
- }];
+const alternativeTokenKeys = [{
+ key: 'label',
+ type: 'string',
+ param: 'name',
+ symbol: '~',
+}];
- const tokenKeysWithAlternative = tokenKeys.concat(alternativeTokenKeys);
+const tokenKeysWithAlternative = tokenKeys.concat(alternativeTokenKeys);
- const conditions = [{
- url: 'assignee_id=0',
- tokenKey: 'assignee',
- value: 'none',
- }, {
- url: 'milestone_title=No+Milestone',
- tokenKey: 'milestone',
- value: 'none',
- }, {
- url: 'milestone_title=%23upcoming',
- tokenKey: 'milestone',
- value: 'upcoming',
- }, {
- url: 'milestone_title=%23started',
- tokenKey: 'milestone',
- value: 'started',
- }, {
- url: 'label_name[]=No+Label',
- tokenKey: 'label',
- value: 'none',
- }];
+const conditions = [{
+ url: 'assignee_id=0',
+ tokenKey: 'assignee',
+ value: 'none',
+}, {
+ url: 'milestone_title=No+Milestone',
+ tokenKey: 'milestone',
+ value: 'none',
+}, {
+ url: 'milestone_title=%23upcoming',
+ tokenKey: 'milestone',
+ value: 'upcoming',
+}, {
+ url: 'milestone_title=%23started',
+ tokenKey: 'milestone',
+ value: 'started',
+}, {
+ url: 'label_name[]=No+Label',
+ tokenKey: 'label',
+ value: 'none',
+}];
- class FilteredSearchTokenKeys {
- static get() {
- return tokenKeys;
- }
+class FilteredSearchTokenKeys {
+ static get() {
+ return tokenKeys;
+ }
- static getAlternatives() {
- return alternativeTokenKeys;
- }
+ static getAlternatives() {
+ return alternativeTokenKeys;
+ }
- static getConditions() {
- return conditions;
- }
+ static getConditions() {
+ return conditions;
+ }
- static searchByKey(key) {
- return tokenKeys.find(tokenKey => tokenKey.key === key) || null;
- }
+ static searchByKey(key) {
+ return tokenKeys.find(tokenKey => tokenKey.key === key) || null;
+ }
- static searchBySymbol(symbol) {
- return tokenKeys.find(tokenKey => tokenKey.symbol === symbol) || null;
- }
+ static searchBySymbol(symbol) {
+ return tokenKeys.find(tokenKey => tokenKey.symbol === symbol) || null;
+ }
- static searchByKeyParam(keyParam) {
- return tokenKeysWithAlternative.find((tokenKey) => {
- let tokenKeyParam = tokenKey.key;
+ static searchByKeyParam(keyParam) {
+ return tokenKeysWithAlternative.find((tokenKey) => {
+ let tokenKeyParam = tokenKey.key;
- if (tokenKey.param) {
- tokenKeyParam += `_${tokenKey.param}`;
- }
+ if (tokenKey.param) {
+ tokenKeyParam += `_${tokenKey.param}`;
+ }
- return keyParam === tokenKeyParam;
- }) || null;
- }
+ return keyParam === tokenKeyParam;
+ }) || null;
+ }
- static searchByConditionUrl(url) {
- return conditions.find(condition => condition.url === url) || null;
- }
+ static searchByConditionUrl(url) {
+ return conditions.find(condition => condition.url === url) || null;
+ }
- static searchByConditionKeyValue(key, value) {
- return conditions
- .find(condition => condition.tokenKey === key && condition.value === value) || null;
- }
+ static searchByConditionKeyValue(key, value) {
+ return conditions
+ .find(condition => condition.tokenKey === key && condition.value === value) || null;
}
+}
- window.gl = window.gl || {};
- gl.FilteredSearchTokenKeys = FilteredSearchTokenKeys;
-})();
+window.gl = window.gl || {};
+gl.FilteredSearchTokenKeys = FilteredSearchTokenKeys;
diff --git a/app/assets/javascripts/filtered_search/filtered_search_tokenizer.js b/app/assets/javascripts/filtered_search/filtered_search_tokenizer.js
index a2729dc0e95..2808e4b238a 100644
--- a/app/assets/javascripts/filtered_search/filtered_search_tokenizer.js
+++ b/app/assets/javascripts/filtered_search/filtered_search_tokenizer.js
@@ -1,58 +1,56 @@
require('./filtered_search_token_keys');
-(() => {
- class FilteredSearchTokenizer {
- static processTokens(input) {
- const allowedKeys = gl.FilteredSearchTokenKeys.get().map(i => i.key);
- // Regex extracts `(token):(symbol)(value)`
- // Values that start with a double quote must end in a double quote (same for single)
- const tokenRegex = new RegExp(`(${allowedKeys.join('|')}):([~%@]?)(?:('[^']*'{0,1})|("[^"]*"{0,1})|(\\S+))`, 'g');
- const tokens = [];
- const tokenIndexes = []; // stores key+value for simple search
- let lastToken = null;
- const searchToken = input.replace(tokenRegex, (match, key, symbol, v1, v2, v3) => {
- let tokenValue = v1 || v2 || v3;
- let tokenSymbol = symbol;
- let tokenIndex = '';
-
- if (tokenValue === '~' || tokenValue === '%' || tokenValue === '@') {
- tokenSymbol = tokenValue;
- tokenValue = '';
- }
-
- tokenIndex = `${key}:${tokenValue}`;
-
- // Prevent adding duplicates
- if (tokenIndexes.indexOf(tokenIndex) === -1) {
- tokenIndexes.push(tokenIndex);
-
- tokens.push({
- key,
- value: tokenValue || '',
- symbol: tokenSymbol || '',
- });
- }
-
- return '';
- }).replace(/\s{2,}/g, ' ').trim() || '';
-
- if (tokens.length > 0) {
- const last = tokens[tokens.length - 1];
- const lastString = `${last.key}:${last.symbol}${last.value}`;
- lastToken = input.lastIndexOf(lastString) ===
- input.length - lastString.length ? last : searchToken;
- } else {
- lastToken = searchToken;
+class FilteredSearchTokenizer {
+ static processTokens(input) {
+ const allowedKeys = gl.FilteredSearchTokenKeys.get().map(i => i.key);
+ // Regex extracts `(token):(symbol)(value)`
+ // Values that start with a double quote must end in a double quote (same for single)
+ const tokenRegex = new RegExp(`(${allowedKeys.join('|')}):([~%@]?)(?:('[^']*'{0,1})|("[^"]*"{0,1})|(\\S+))`, 'g');
+ const tokens = [];
+ const tokenIndexes = []; // stores key+value for simple search
+ let lastToken = null;
+ const searchToken = input.replace(tokenRegex, (match, key, symbol, v1, v2, v3) => {
+ let tokenValue = v1 || v2 || v3;
+ let tokenSymbol = symbol;
+ let tokenIndex = '';
+
+ if (tokenValue === '~' || tokenValue === '%' || tokenValue === '@') {
+ tokenSymbol = tokenValue;
+ tokenValue = '';
}
- return {
- tokens,
- lastToken,
- searchToken,
- };
+ tokenIndex = `${key}:${tokenValue}`;
+
+ // Prevent adding duplicates
+ if (tokenIndexes.indexOf(tokenIndex) === -1) {
+ tokenIndexes.push(tokenIndex);
+
+ tokens.push({
+ key,
+ value: tokenValue || '',
+ symbol: tokenSymbol || '',
+ });
+ }
+
+ return '';
+ }).replace(/\s{2,}/g, ' ').trim() || '';
+
+ if (tokens.length > 0) {
+ const last = tokens[tokens.length - 1];
+ const lastString = `${last.key}:${last.symbol}${last.value}`;
+ lastToken = input.lastIndexOf(lastString) ===
+ input.length - lastString.length ? last : searchToken;
+ } else {
+ lastToken = searchToken;
}
+
+ return {
+ tokens,
+ lastToken,
+ searchToken,
+ };
}
+}
- window.gl = window.gl || {};
- gl.FilteredSearchTokenizer = FilteredSearchTokenizer;
-})();
+window.gl = window.gl || {};
+gl.FilteredSearchTokenizer = FilteredSearchTokenizer;
diff --git a/app/assets/javascripts/vue_pipelines_index/components/async_button.vue b/app/assets/javascripts/pipelines/components/async_button.vue
index d1c60b570de..d1c60b570de 100644
--- a/app/assets/javascripts/vue_pipelines_index/components/async_button.vue
+++ b/app/assets/javascripts/pipelines/components/async_button.vue
diff --git a/app/assets/javascripts/vue_pipelines_index/components/empty_state.vue b/app/assets/javascripts/pipelines/components/empty_state.vue
index ba158bc4a1e..ba158bc4a1e 100644
--- a/app/assets/javascripts/vue_pipelines_index/components/empty_state.vue
+++ b/app/assets/javascripts/pipelines/components/empty_state.vue
diff --git a/app/assets/javascripts/vue_pipelines_index/components/error_state.vue b/app/assets/javascripts/pipelines/components/error_state.vue
index 90cee68163e..90cee68163e 100644
--- a/app/assets/javascripts/vue_pipelines_index/components/error_state.vue
+++ b/app/assets/javascripts/pipelines/components/error_state.vue
diff --git a/app/assets/javascripts/vue_pipelines_index/components/nav_controls.js b/app/assets/javascripts/pipelines/components/nav_controls.js
index 6aa10531034..6aa10531034 100644
--- a/app/assets/javascripts/vue_pipelines_index/components/nav_controls.js
+++ b/app/assets/javascripts/pipelines/components/nav_controls.js
diff --git a/app/assets/javascripts/vue_pipelines_index/components/navigation_tabs.js b/app/assets/javascripts/pipelines/components/navigation_tabs.js
index 1626ae17a30..1626ae17a30 100644
--- a/app/assets/javascripts/vue_pipelines_index/components/navigation_tabs.js
+++ b/app/assets/javascripts/pipelines/components/navigation_tabs.js
diff --git a/app/assets/javascripts/vue_pipelines_index/components/pipeline_url.js b/app/assets/javascripts/pipelines/components/pipeline_url.js
index 4e183d5c8ec..4e183d5c8ec 100644
--- a/app/assets/javascripts/vue_pipelines_index/components/pipeline_url.js
+++ b/app/assets/javascripts/pipelines/components/pipeline_url.js
diff --git a/app/assets/javascripts/vue_pipelines_index/components/pipelines_actions.js b/app/assets/javascripts/pipelines/components/pipelines_actions.js
index 535064f610e..535064f610e 100644
--- a/app/assets/javascripts/vue_pipelines_index/components/pipelines_actions.js
+++ b/app/assets/javascripts/pipelines/components/pipelines_actions.js
diff --git a/app/assets/javascripts/vue_pipelines_index/components/pipelines_artifacts.js b/app/assets/javascripts/pipelines/components/pipelines_artifacts.js
index f18e2dfadaf..f18e2dfadaf 100644
--- a/app/assets/javascripts/vue_pipelines_index/components/pipelines_artifacts.js
+++ b/app/assets/javascripts/pipelines/components/pipelines_artifacts.js
diff --git a/app/assets/javascripts/vue_pipelines_index/components/stage.js b/app/assets/javascripts/pipelines/components/stage.js
index b8cc3630611..b8cc3630611 100644
--- a/app/assets/javascripts/vue_pipelines_index/components/stage.js
+++ b/app/assets/javascripts/pipelines/components/stage.js
diff --git a/app/assets/javascripts/vue_pipelines_index/components/status.js b/app/assets/javascripts/pipelines/components/status.js
index 21a281af438..21a281af438 100644
--- a/app/assets/javascripts/vue_pipelines_index/components/status.js
+++ b/app/assets/javascripts/pipelines/components/status.js
diff --git a/app/assets/javascripts/vue_pipelines_index/components/time_ago.js b/app/assets/javascripts/pipelines/components/time_ago.js
index 498d0715f54..498d0715f54 100644
--- a/app/assets/javascripts/vue_pipelines_index/components/time_ago.js
+++ b/app/assets/javascripts/pipelines/components/time_ago.js
diff --git a/app/assets/javascripts/vue_pipelines_index/event_hub.js b/app/assets/javascripts/pipelines/event_hub.js
index 0948c2e5352..0948c2e5352 100644
--- a/app/assets/javascripts/vue_pipelines_index/event_hub.js
+++ b/app/assets/javascripts/pipelines/event_hub.js
diff --git a/app/assets/javascripts/vue_pipelines_index/index.js b/app/assets/javascripts/pipelines/index.js
index 48f9181a8d9..48f9181a8d9 100644
--- a/app/assets/javascripts/vue_pipelines_index/index.js
+++ b/app/assets/javascripts/pipelines/index.js
diff --git a/app/assets/javascripts/vue_pipelines_index/pipelines.js b/app/assets/javascripts/pipelines/pipelines.js
index 6eea4812f33..6eea4812f33 100644
--- a/app/assets/javascripts/vue_pipelines_index/pipelines.js
+++ b/app/assets/javascripts/pipelines/pipelines.js
diff --git a/app/assets/javascripts/vue_pipelines_index/services/pipelines_service.js b/app/assets/javascripts/pipelines/services/pipelines_service.js
index 255cd513490..255cd513490 100644
--- a/app/assets/javascripts/vue_pipelines_index/services/pipelines_service.js
+++ b/app/assets/javascripts/pipelines/services/pipelines_service.js
diff --git a/app/assets/javascripts/vue_pipelines_index/stores/pipelines_store.js b/app/assets/javascripts/pipelines/stores/pipelines_store.js
index 377ec8ba2cc..377ec8ba2cc 100644
--- a/app/assets/javascripts/vue_pipelines_index/stores/pipelines_store.js
+++ b/app/assets/javascripts/pipelines/stores/pipelines_store.js
diff --git a/app/assets/javascripts/vue_shared/components/pipelines_table_row.js b/app/assets/javascripts/vue_shared/components/pipelines_table_row.js
index 8ebe12cb1c5..62b7131de51 100644
--- a/app/assets/javascripts/vue_shared/components/pipelines_table_row.js
+++ b/app/assets/javascripts/vue_shared/components/pipelines_table_row.js
@@ -1,12 +1,12 @@
/* eslint-disable no-param-reassign */
-import AsyncButtonComponent from '../../vue_pipelines_index/components/async_button.vue';
-import PipelinesActionsComponent from '../../vue_pipelines_index/components/pipelines_actions';
-import PipelinesArtifactsComponent from '../../vue_pipelines_index/components/pipelines_artifacts';
-import PipelinesStatusComponent from '../../vue_pipelines_index/components/status';
-import PipelinesStageComponent from '../../vue_pipelines_index/components/stage';
-import PipelinesUrlComponent from '../../vue_pipelines_index/components/pipeline_url';
-import PipelinesTimeagoComponent from '../../vue_pipelines_index/components/time_ago';
+import AsyncButtonComponent from '../../pipelines/components/async_button.vue';
+import PipelinesActionsComponent from '../../pipelines/components/pipelines_actions';
+import PipelinesArtifactsComponent from '../../pipelines/components/pipelines_artifacts';
+import PipelinesStatusComponent from '../../pipelines/components/status';
+import PipelinesStageComponent from '../../pipelines/components/stage';
+import PipelinesUrlComponent from '../../pipelines/components/pipeline_url';
+import PipelinesTimeagoComponent from '../../pipelines/components/time_ago';
import CommitComponent from './commit';
/**