IssueNotesRefactor: Implement jumping to target note.

This commit is contained in:
Fatih Acet 2017-07-07 02:13:10 +03:00
parent b72db79668
commit 993936fbd0
6 changed files with 60 additions and 4 deletions

View File

@ -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);
}; };

View File

@ -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"

View File

@ -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" />

View File

@ -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

View File

@ -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>

View File

@ -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);