summaryrefslogtreecommitdiff
path: root/app/assets/javascripts/notes
diff options
context:
space:
mode:
authorGitLab Bot <gitlab-bot@gitlab.com>2020-03-26 09:07:52 +0000
committerGitLab Bot <gitlab-bot@gitlab.com>2020-03-26 09:07:52 +0000
commit7e019504f5ac6decde690565857238e7e59aa034 (patch)
treefab8832b40e25fc9bc1ae54b9303b95ea146b5d5 /app/assets/javascripts/notes
parent116d4e56e83a1f408afe710ce070e699ba206475 (diff)
downloadgitlab-ce-7e019504f5ac6decde690565857238e7e59aa034.tar.gz
Add latest changes from gitlab-org/gitlab@master
Diffstat (limited to 'app/assets/javascripts/notes')
-rw-r--r--app/assets/javascripts/notes/components/notes_app.vue93
-rw-r--r--app/assets/javascripts/notes/components/sort_discussion.vue64
-rw-r--r--app/assets/javascripts/notes/constants.js2
-rw-r--r--app/assets/javascripts/notes/index.js2
-rw-r--r--app/assets/javascripts/notes/sort_discussions.js16
-rw-r--r--app/assets/javascripts/notes/stores/actions.js4
-rw-r--r--app/assets/javascripts/notes/stores/getters.js19
-rw-r--r--app/assets/javascripts/notes/stores/modules/index.js2
-rw-r--r--app/assets/javascripts/notes/stores/mutation_types.js1
-rw-r--r--app/assets/javascripts/notes/stores/mutations.js4
10 files changed, 168 insertions, 39 deletions
diff --git a/app/assets/javascripts/notes/components/notes_app.vue b/app/assets/javascripts/notes/components/notes_app.vue
index 762228dd138..c1dd56aedf2 100644
--- a/app/assets/javascripts/notes/components/notes_app.vue
+++ b/app/assets/javascripts/notes/components/notes_app.vue
@@ -12,6 +12,7 @@ import commentForm from './comment_form.vue';
import placeholderNote from '../../vue_shared/components/notes/placeholder_note.vue';
import placeholderSystemNote from '../../vue_shared/components/notes/placeholder_system_note.vue';
import skeletonLoadingContainer from '../../vue_shared/components/notes/skeleton_note.vue';
+import OrderedLayout from '~/vue_shared/components/ordered_layout.vue';
import highlightCurrentUser from '~/behaviors/markdown/highlight_current_user';
import { __ } from '~/locale';
import initUserPopovers from '~/user_popovers';
@@ -27,6 +28,7 @@ export default {
placeholderSystemNote,
skeletonLoadingContainer,
discussionFilterNote,
+ OrderedLayout,
},
props: {
noteableData: {
@@ -70,7 +72,11 @@ export default {
'getNoteableData',
'userCanReply',
'discussionTabCounter',
+ 'sortDirection',
]),
+ sortDirDesc() {
+ return this.sortDirection === constants.DESC;
+ },
discussionTabCounterText() {
return this.isLoading ? '' : this.discussionTabCounter;
},
@@ -91,6 +97,9 @@ export default {
canReply() {
return this.userCanReply && !this.commentsDisabled;
},
+ slotKeys() {
+ return this.sortDirDesc ? ['form', 'comments'] : ['comments', 'form'];
+ },
},
watch: {
shouldShow() {
@@ -156,6 +165,9 @@ export default {
'convertToDiscussion',
'stopPolling',
]),
+ discussionIsIndividualNoteAndNotConverted(discussion) {
+ return discussion.individual_note && !this.convertedDisscussionIds.includes(discussion.id);
+ },
handleHashChanged() {
const noteId = this.checkLocationHash();
@@ -232,44 +244,51 @@ export default {
<template>
<div v-show="shouldShow" id="notes">
- <ul id="notes-list" class="notes main-notes-list timeline">
- <template v-for="discussion in allDiscussions">
- <skeleton-loading-container v-if="discussion.isSkeletonNote" :key="discussion.id" />
- <template v-else-if="discussion.isPlaceholderNote">
- <placeholder-system-note
- v-if="discussion.placeholderType === $options.systemNote"
- :key="discussion.id"
- :note="discussion.notes[0]"
- />
- <placeholder-note v-else :key="discussion.id" :note="discussion.notes[0]" />
- </template>
- <template
- v-else-if="discussion.individual_note && !convertedDisscussionIds.includes(discussion.id)"
- >
- <system-note
- v-if="discussion.notes[0].system"
- :key="discussion.id"
- :note="discussion.notes[0]"
- />
- <noteable-note
- v-else
- :key="discussion.id"
- :note="discussion.notes[0]"
- :show-reply-button="canReply"
- @startReplying="startReplying(discussion.id)"
- />
- </template>
- <noteable-discussion
- v-else
- :key="discussion.id"
- :discussion="discussion"
- :render-diff-file="true"
- :help-page-path="helpPagePath"
+ <ordered-layout :slot-keys="slotKeys">
+ <template #form>
+ <comment-form
+ v-if="!commentsDisabled"
+ class="js-comment-form"
+ :noteable-type="noteableType"
/>
</template>
- <discussion-filter-note v-show="commentsDisabled" />
- </ul>
-
- <comment-form v-if="!commentsDisabled" :noteable-type="noteableType" />
+ <template #comments>
+ <ul id="notes-list" class="notes main-notes-list timeline">
+ <template v-for="discussion in allDiscussions">
+ <skeleton-loading-container v-if="discussion.isSkeletonNote" :key="discussion.id" />
+ <template v-else-if="discussion.isPlaceholderNote">
+ <placeholder-system-note
+ v-if="discussion.placeholderType === $options.systemNote"
+ :key="discussion.id"
+ :note="discussion.notes[0]"
+ />
+ <placeholder-note v-else :key="discussion.id" :note="discussion.notes[0]" />
+ </template>
+ <template v-else-if="discussionIsIndividualNoteAndNotConverted(discussion)">
+ <system-note
+ v-if="discussion.notes[0].system"
+ :key="discussion.id"
+ :note="discussion.notes[0]"
+ />
+ <noteable-note
+ v-else
+ :key="discussion.id"
+ :note="discussion.notes[0]"
+ :show-reply-button="canReply"
+ @startReplying="startReplying(discussion.id)"
+ />
+ </template>
+ <noteable-discussion
+ v-else
+ :key="discussion.id"
+ :discussion="discussion"
+ :render-diff-file="true"
+ :help-page-path="helpPagePath"
+ />
+ </template>
+ <discussion-filter-note v-show="commentsDisabled" />
+ </ul>
+ </template>
+ </ordered-layout>
</div>
</template>
diff --git a/app/assets/javascripts/notes/components/sort_discussion.vue b/app/assets/javascripts/notes/components/sort_discussion.vue
new file mode 100644
index 00000000000..16eded52763
--- /dev/null
+++ b/app/assets/javascripts/notes/components/sort_discussion.vue
@@ -0,0 +1,64 @@
+<script>
+import { GlIcon } from '@gitlab/ui';
+import { mapActions, mapGetters } from 'vuex';
+import { __ } from '~/locale';
+import { ASC, DESC } from '../constants';
+
+const SORT_OPTIONS = [
+ { key: DESC, text: __('Newest first'), cls: 'js-newest-first' },
+ { key: ASC, text: __('Oldest first'), cls: 'js-oldest-first' },
+];
+
+export default {
+ SORT_OPTIONS,
+ components: {
+ GlIcon,
+ },
+ computed: {
+ ...mapGetters(['sortDirection']),
+ selectedOption() {
+ return SORT_OPTIONS.find(({ key }) => this.sortDirection === key);
+ },
+ dropdownText() {
+ return this.selectedOption.text;
+ },
+ },
+ methods: {
+ ...mapActions(['setDiscussionSortDirection']),
+ fetchSortedDiscussions(direction) {
+ if (this.isDropdownItemActive(direction)) {
+ return;
+ }
+
+ this.setDiscussionSortDirection(direction);
+ },
+ isDropdownItemActive(sortDir) {
+ return sortDir === this.sortDirection;
+ },
+ },
+};
+</script>
+
+<template>
+ <div class="mr-2 d-inline-block align-bottom full-width-mobile">
+ <button class="btn btn-sm js-dropdown-text" data-toggle="dropdown" aria-expanded="false">
+ {{ dropdownText }}
+ <gl-icon name="chevron-down" />
+ </button>
+ <div ref="dropdownMenu" class="dropdown-menu dropdown-menu-selectable dropdown-menu-right">
+ <div class="dropdown-content">
+ <ul>
+ <li v-for="{ text, key, cls } in $options.SORT_OPTIONS" :key="key">
+ <button
+ :class="[cls, { 'is-active': isDropdownItemActive(key) }]"
+ type="button"
+ @click="fetchSortedDiscussions(key)"
+ >
+ {{ text }}
+ </button>
+ </li>
+ </ul>
+ </div>
+ </div>
+ </div>
+</template>
diff --git a/app/assets/javascripts/notes/constants.js b/app/assets/javascripts/notes/constants.js
index e9a81bc9553..c1449237f8a 100644
--- a/app/assets/javascripts/notes/constants.js
+++ b/app/assets/javascripts/notes/constants.js
@@ -19,6 +19,8 @@ export const DISCUSSION_FILTERS_DEFAULT_VALUE = 0;
export const DISCUSSION_TAB_LABEL = 'show';
export const NOTE_UNDERSCORE = 'note_';
export const TIME_DIFFERENCE_VALUE = 10;
+export const ASC = 'asc';
+export const DESC = 'desc';
export const NOTEABLE_TYPE_MAPPING = {
Issue: ISSUE_NOTEABLE_TYPE,
diff --git a/app/assets/javascripts/notes/index.js b/app/assets/javascripts/notes/index.js
index 30372103590..8f9e2359e0d 100644
--- a/app/assets/javascripts/notes/index.js
+++ b/app/assets/javascripts/notes/index.js
@@ -1,6 +1,7 @@
import Vue from 'vue';
import notesApp from './components/notes_app.vue';
import initDiscussionFilters from './discussion_filters';
+import initSortDiscussions from './sort_discussions';
import createStore from './stores';
document.addEventListener('DOMContentLoaded', () => {
@@ -50,4 +51,5 @@ document.addEventListener('DOMContentLoaded', () => {
});
initDiscussionFilters(store);
+ initSortDiscussions(store);
});
diff --git a/app/assets/javascripts/notes/sort_discussions.js b/app/assets/javascripts/notes/sort_discussions.js
new file mode 100644
index 00000000000..a06c23f5f76
--- /dev/null
+++ b/app/assets/javascripts/notes/sort_discussions.js
@@ -0,0 +1,16 @@
+import Vue from 'vue';
+import SortDiscussion from './components/sort_discussion.vue';
+
+export default store => {
+ const el = document.getElementById('js-vue-sort-issue-discussions');
+
+ if (!el) return null;
+
+ return new Vue({
+ el,
+ store,
+ render(createElement) {
+ return createElement(SortDiscussion);
+ },
+ });
+};
diff --git a/app/assets/javascripts/notes/stores/actions.js b/app/assets/javascripts/notes/stores/actions.js
index accc37121d0..1b80b59621a 100644
--- a/app/assets/javascripts/notes/stores/actions.js
+++ b/app/assets/javascripts/notes/stores/actions.js
@@ -69,6 +69,10 @@ export const updateDiscussion = ({ commit, state }, discussion) => {
return utils.findNoteObjectById(state.discussions, discussion.id);
};
+export const setDiscussionSortDirection = ({ commit }, direction) => {
+ commit(types.SET_DISCUSSIONS_SORT, direction);
+};
+
export const removeNote = ({ commit, dispatch, state }, note) => {
const discussion = state.discussions.find(({ id }) => id === note.discussion_id);
diff --git a/app/assets/javascripts/notes/stores/getters.js b/app/assets/javascripts/notes/stores/getters.js
index 28cc9cdd7e9..eb877083bca 100644
--- a/app/assets/javascripts/notes/stores/getters.js
+++ b/app/assets/javascripts/notes/stores/getters.js
@@ -1,8 +1,16 @@
-import { flattenDeep } from 'lodash';
+import { flattenDeep, clone } from 'lodash';
import * as constants from '../constants';
import { collapseSystemNotes } from './collapse_utils';
-export const discussions = state => collapseSystemNotes(state.discussions);
+export const discussions = state => {
+ let discussionsInState = clone(state.discussions);
+ // NOTE: not testing bc will be removed when backend is finished.
+ if (state.discussionSortOrder === constants.DESC) {
+ discussionsInState = discussionsInState.reverse();
+ }
+
+ return collapseSystemNotes(discussionsInState);
+};
export const convertedDisscussionIds = state => state.convertedDisscussionIds;
@@ -12,6 +20,13 @@ export const getNotesData = state => state.notesData;
export const isNotesFetched = state => state.isNotesFetched;
+/*
+ * WARNING: This is an example of an "unnecessary" getter
+ * more info found here: https://gitlab.com/groups/gitlab-org/-/epics/2913.
+ */
+
+export const sortDirection = state => state.discussionSortOrder;
+
export const isLoading = state => state.isLoading;
export const getNotesDataByProp = state => prop => state.notesData[prop];
diff --git a/app/assets/javascripts/notes/stores/modules/index.js b/app/assets/javascripts/notes/stores/modules/index.js
index 2d317dcd7da..81844ad6e98 100644
--- a/app/assets/javascripts/notes/stores/modules/index.js
+++ b/app/assets/javascripts/notes/stores/modules/index.js
@@ -1,10 +1,12 @@
import * as actions from '../actions';
import * as getters from '../getters';
import mutations from '../mutations';
+import { ASC } from '../../constants';
export default () => ({
state: {
discussions: [],
+ discussionSortOrder: ASC,
convertedDisscussionIds: [],
targetNoteHash: null,
lastFetchedAt: null,
diff --git a/app/assets/javascripts/notes/stores/mutation_types.js b/app/assets/javascripts/notes/stores/mutation_types.js
index 0cc59f9150c..5b7225bb3d2 100644
--- a/app/assets/javascripts/notes/stores/mutation_types.js
+++ b/app/assets/javascripts/notes/stores/mutation_types.js
@@ -27,6 +27,7 @@ export const TOGGLE_DISCUSSION = 'TOGGLE_DISCUSSION';
export const SET_EXPAND_DISCUSSIONS = 'SET_EXPAND_DISCUSSIONS';
export const UPDATE_RESOLVABLE_DISCUSSIONS_COUNTS = 'UPDATE_RESOLVABLE_DISCUSSIONS_COUNTS';
export const SET_CURRENT_DISCUSSION_ID = 'SET_CURRENT_DISCUSSION_ID';
+export const SET_DISCUSSIONS_SORT = 'SET_DISCUSSIONS_SORT';
// Issue
export const CLOSE_ISSUE = 'CLOSE_ISSUE';
diff --git a/app/assets/javascripts/notes/stores/mutations.js b/app/assets/javascripts/notes/stores/mutations.js
index 68bf8394508..028fc87198c 100644
--- a/app/assets/javascripts/notes/stores/mutations.js
+++ b/app/assets/javascripts/notes/stores/mutations.js
@@ -263,6 +263,10 @@ export default {
discussion.truncated_diff_lines = utils.prepareDiffLines(diffLines);
},
+ [types.SET_DISCUSSIONS_SORT](state, sort) {
+ state.discussionSortOrder = sort;
+ },
+
[types.DISABLE_COMMENTS](state, value) {
state.commentsDisabled = value;
},