IssueNotesRefactor: Implement jumping to target note.
This commit is contained in:
parent
b72db79668
commit
993936fbd0
6 changed files with 60 additions and 4 deletions
|
@ -162,10 +162,11 @@
|
||||||
|
|
||||||
gl.utils.scrollToElement = function($el) {
|
gl.utils.scrollToElement = function($el) {
|
||||||
var top = $el.offset().top;
|
var top = $el.offset().top;
|
||||||
gl.mrTabsHeight = gl.mrTabsHeight || $('.merge-request-tabs').height();
|
var mrTabsHeight = $('.merge-request-tabs').height() || 0;
|
||||||
|
var headerHeight = $('.navbar-gitlab').height() || 0;
|
||||||
|
|
||||||
return $('body, html').animate({
|
return $('body, html').animate({
|
||||||
scrollTop: top - (gl.mrTabsHeight)
|
scrollTop: top - mrTabsHeight - headerHeight
|
||||||
}, 200);
|
}, 200);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
<script>
|
<script>
|
||||||
/* global Flash */
|
/* global Flash */
|
||||||
|
|
||||||
|
import { mapGetters } from 'vuex';
|
||||||
import UserAvatarLink from '../../vue_shared/components/user_avatar/user_avatar_link.vue';
|
import UserAvatarLink from '../../vue_shared/components/user_avatar/user_avatar_link.vue';
|
||||||
import IssueNoteHeader from './issue_note_header.vue';
|
import IssueNoteHeader from './issue_note_header.vue';
|
||||||
import IssueNoteActions from './issue_note_actions.vue';
|
import IssueNoteActions from './issue_note_actions.vue';
|
||||||
|
@ -26,6 +27,9 @@ export default {
|
||||||
IssueNoteBody,
|
IssueNoteBody,
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
|
...mapGetters([
|
||||||
|
'targetNoteHash',
|
||||||
|
]),
|
||||||
author() {
|
author() {
|
||||||
return this.note.author;
|
return this.note.author;
|
||||||
},
|
},
|
||||||
|
@ -33,11 +37,15 @@ export default {
|
||||||
return {
|
return {
|
||||||
'is-editing': this.isEditing,
|
'is-editing': this.isEditing,
|
||||||
'disabled-content': this.isDeleting,
|
'disabled-content': this.isDeleting,
|
||||||
|
target: this.targetNoteHash === this.noteAnchorId,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
canReportAsAbuse() {
|
canReportAsAbuse() {
|
||||||
return this.note.report_abuse_path && this.author.id !== window.gon.current_user_id;
|
return this.note.report_abuse_path && this.author.id !== window.gon.current_user_id;
|
||||||
},
|
},
|
||||||
|
noteAnchorId() {
|
||||||
|
return `note_${this.note.id}`;
|
||||||
|
},
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
editHandler() {
|
editHandler() {
|
||||||
|
@ -98,6 +106,7 @@ export default {
|
||||||
<template>
|
<template>
|
||||||
<li
|
<li
|
||||||
class="note timeline-entry"
|
class="note timeline-entry"
|
||||||
|
:id="noteAnchorId"
|
||||||
:class="classNameBindings">
|
:class="classNameBindings">
|
||||||
<div class="timeline-entry-inner">
|
<div class="timeline-entry-inner">
|
||||||
<div class="timeline-icon">
|
<div class="timeline-icon">
|
||||||
|
@ -115,6 +124,7 @@ export default {
|
||||||
:noteId="note.id"
|
:noteId="note.id"
|
||||||
actionText="commented" />
|
actionText="commented" />
|
||||||
<issue-note-actions
|
<issue-note-actions
|
||||||
|
:authorId="author.id"
|
||||||
:accessLevel="note.human_access"
|
:accessLevel="note.human_access"
|
||||||
:canAward="note.emoji_awardable"
|
:canAward="note.emoji_awardable"
|
||||||
:canEdit="note.current_user.can_edit"
|
:canEdit="note.current_user.can_edit"
|
||||||
|
|
|
@ -56,6 +56,9 @@ export default {
|
||||||
this.isExpanded = !this.isExpanded;
|
this.isExpanded = !this.isExpanded;
|
||||||
this.toggleHandler();
|
this.toggleHandler();
|
||||||
},
|
},
|
||||||
|
updateTargetNoteHash() {
|
||||||
|
this.$store.commit('setTargetNoteHash', this.noteTimestampLink);
|
||||||
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
@ -79,7 +82,9 @@ export default {
|
||||||
v-if="actionTextHtml"
|
v-if="actionTextHtml"
|
||||||
v-html="actionTextHtml"
|
v-html="actionTextHtml"
|
||||||
class="system-note-message"></span>
|
class="system-note-message"></span>
|
||||||
<a :href="noteTimestampLink">
|
<a
|
||||||
|
:href="noteTimestampLink"
|
||||||
|
@click="updateTargetNoteHash">
|
||||||
<time-ago-tooltip
|
<time-ago-tooltip
|
||||||
:time="createdAt"
|
:time="createdAt"
|
||||||
tooltipPlacement="bottom" />
|
tooltipPlacement="bottom" />
|
||||||
|
|
|
@ -43,6 +43,19 @@ export default {
|
||||||
componentData(note) {
|
componentData(note) {
|
||||||
return note.individual_note ? note.notes[0] : note;
|
return note.individual_note ? note.notes[0] : note;
|
||||||
},
|
},
|
||||||
|
checkLocationHash() {
|
||||||
|
const hash = gl.utils.getLocationHash();
|
||||||
|
const $el = $(`#${hash}`);
|
||||||
|
|
||||||
|
if (hash && $el) {
|
||||||
|
const isInViewport = gl.utils.isInViewport($el[0]);
|
||||||
|
this.$store.commit('setTargetNoteHash', hash);
|
||||||
|
|
||||||
|
if (!isInViewport) {
|
||||||
|
gl.utils.scrollToElement($el);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
},
|
},
|
||||||
mounted() {
|
mounted() {
|
||||||
const { discussionsPath, notesPath, lastFetchedAt } = this.$el.parentNode.dataset;
|
const { discussionsPath, notesPath, lastFetchedAt } = this.$el.parentNode.dataset;
|
||||||
|
@ -50,6 +63,11 @@ export default {
|
||||||
this.$store.dispatch('fetchNotes', discussionsPath)
|
this.$store.dispatch('fetchNotes', discussionsPath)
|
||||||
.then(() => {
|
.then(() => {
|
||||||
this.isLoading = false;
|
this.isLoading = false;
|
||||||
|
|
||||||
|
// Scroll to note if we have hash fragment in the page URL
|
||||||
|
Vue.nextTick(() => {
|
||||||
|
this.checkLocationHash();
|
||||||
|
});
|
||||||
})
|
})
|
||||||
.catch(() => {
|
.catch(() => {
|
||||||
new Flash('Something went wrong while fetching issue comments. Please try again.'); // eslint-disable-line
|
new Flash('Something went wrong while fetching issue comments. Please try again.'); // eslint-disable-line
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
<script>
|
<script>
|
||||||
|
import { mapGetters } from 'vuex';
|
||||||
import iconsMap from './issue_note_icons';
|
import iconsMap from './issue_note_icons';
|
||||||
import IssueNoteHeader from './issue_note_header.vue';
|
import IssueNoteHeader from './issue_note_header.vue';
|
||||||
|
|
||||||
|
@ -17,11 +18,25 @@ export default {
|
||||||
components: {
|
components: {
|
||||||
IssueNoteHeader,
|
IssueNoteHeader,
|
||||||
},
|
},
|
||||||
|
computed: {
|
||||||
|
...mapGetters([
|
||||||
|
'targetNoteHash',
|
||||||
|
]),
|
||||||
|
noteAnchorId() {
|
||||||
|
return `note_${this.note.id}`;
|
||||||
|
},
|
||||||
|
isTargetNote() {
|
||||||
|
return this.targetNoteHash === this.noteAnchorId;
|
||||||
|
},
|
||||||
|
},
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<li class="note system-note timeline-entry">
|
<li
|
||||||
|
:id="noteAnchorId"
|
||||||
|
:class="{ target: isTargetNote }"
|
||||||
|
class="note system-note timeline-entry">
|
||||||
<div class="timeline-entry-inner">
|
<div class="timeline-entry-inner">
|
||||||
<div class="timeline-icon">
|
<div class="timeline-icon">
|
||||||
<span v-html="svg"></span>
|
<span v-html="svg"></span>
|
||||||
|
|
|
@ -6,18 +6,25 @@ const findNoteObjectById = (notes, id) => notes.filter(n => n.id === id)[0];
|
||||||
|
|
||||||
const state = {
|
const state = {
|
||||||
notes: [],
|
notes: [],
|
||||||
|
targetNoteHash: null,
|
||||||
};
|
};
|
||||||
|
|
||||||
const getters = {
|
const getters = {
|
||||||
notes(storeState) {
|
notes(storeState) {
|
||||||
return storeState.notes;
|
return storeState.notes;
|
||||||
},
|
},
|
||||||
|
targetNoteHash(storeState) {
|
||||||
|
return storeState.targetNoteHash;
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
const mutations = {
|
const mutations = {
|
||||||
setInitialNotes(storeState, notes) {
|
setInitialNotes(storeState, notes) {
|
||||||
storeState.notes = notes;
|
storeState.notes = notes;
|
||||||
},
|
},
|
||||||
|
setTargetNoteHash(storeState, hash) {
|
||||||
|
storeState.targetNoteHash = hash;
|
||||||
|
},
|
||||||
toggleDiscussion(storeState, { discussionId }) {
|
toggleDiscussion(storeState, { discussionId }) {
|
||||||
const discussion = findNoteObjectById(storeState.notes, discussionId);
|
const discussion = findNoteObjectById(storeState.notes, discussionId);
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue