[ci skip] Add issue data and notes data provided through haml to the store to stop querying the DOM everywhere
This commit is contained in:
parent
487ed06f44
commit
d34c620fae
11 changed files with 154 additions and 110 deletions
|
@ -1,7 +1,7 @@
|
|||
<script>
|
||||
/* global Flash */
|
||||
|
||||
import { mapActions } from 'vuex';
|
||||
import { mapActions, mapGetters } from 'vuex';
|
||||
import userAvatarLink from '../../vue_shared/components/user_avatar/user_avatar_link.vue';
|
||||
import markdownField from '../../vue_shared/components/markdown/field.vue';
|
||||
import issueNoteSignedOutWidget from './issue_note_signed_out_widget.vue';
|
||||
|
@ -30,6 +30,10 @@
|
|||
issueNoteSignedOutWidget,
|
||||
},
|
||||
computed: {
|
||||
...mapGetters([
|
||||
'getNotesDataByProp',
|
||||
'getIssueDataByProp',
|
||||
]),
|
||||
isLoggedIn() {
|
||||
return window.gon.current_user_id;
|
||||
},
|
||||
|
@ -57,8 +61,7 @@
|
|||
};
|
||||
},
|
||||
canUpdateIssue() {
|
||||
const { issueData } = window.gl;
|
||||
return issueData && issueData.current_user.can_update;
|
||||
return this.getIssueDataByProp(current_user).can_update;
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
|
@ -146,11 +149,8 @@
|
|||
},
|
||||
},
|
||||
mounted() {
|
||||
const issuableDataEl = document.getElementById('js-issuable-app-initial-data');
|
||||
const issueData = JSON.parse(issuableDataEl.innerHTML.replace(/"/g, '"'));
|
||||
|
||||
this.markdownDocsUrl = issueData.markdownDocs;
|
||||
this.quickActionsDocsUrl = issueData.quickActionsDocs;
|
||||
this.markdownDocsUrl = this.getIssueDataByProp(markdownDocs);
|
||||
this.quickActionsDocsUrl = this.getIssueDataByProp(quickActionsDocs);
|
||||
|
||||
eventHub.$on('issueStateChanged', (isClosed) => {
|
||||
this.issueState = isClosed ? constants.CLOSED : constants.REOPENED;
|
||||
|
|
|
@ -5,7 +5,6 @@
|
|||
import { mapGetters, mapActions, mapMutations } from 'vuex';
|
||||
import store from '../stores/';
|
||||
import * as constants from '../constants'
|
||||
import * as types from '../stores/mutation_types';
|
||||
import eventHub from '../event_hub';
|
||||
import issueNote from './issue_note.vue';
|
||||
import issueDiscussion from './issue_discussion.vue';
|
||||
|
@ -17,6 +16,16 @@
|
|||
|
||||
export default {
|
||||
name: 'IssueNotes',
|
||||
props: {
|
||||
issueData: {
|
||||
type: Object,
|
||||
required: true,
|
||||
},
|
||||
notesData: {
|
||||
type: Object,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
store,
|
||||
data() {
|
||||
return {
|
||||
|
@ -36,20 +45,19 @@
|
|||
...mapGetters([
|
||||
'notes',
|
||||
'notesById',
|
||||
'getNotesData',
|
||||
'getNotesDataByProp',
|
||||
'setLastFetchedAt',
|
||||
'setTargetNoteHash',
|
||||
]),
|
||||
},
|
||||
methods: {
|
||||
...mapActions({
|
||||
actionFetchNotes: 'fetchNotes',
|
||||
}),
|
||||
...mapActions([
|
||||
'poll',
|
||||
'toggleAward',
|
||||
'scrollToNoteIfNeeded',
|
||||
]),
|
||||
...mapMutations({
|
||||
setLastFetchedAt: types.SET_LAST_FETCHED_AT,
|
||||
setTargetNoteHash: types.SET_TARGET_NOTE_HASH,
|
||||
poll: 'poll',
|
||||
toggleAward: 'toggleAward',
|
||||
scrollToNoteIfNeeded: 'scrollToNoteIfNeeded',
|
||||
setNotesData: 'setNotesData'
|
||||
}),
|
||||
getComponentName(note) {
|
||||
if (note.isPlaceholderNote) {
|
||||
|
@ -67,9 +75,7 @@
|
|||
return note.individual_note ? note.notes[0] : note;
|
||||
},
|
||||
fetchNotes() {
|
||||
const { discussionsPath } = this.$el.parentNode.dataset;
|
||||
|
||||
this.actionFetchNotes(discussionsPath)
|
||||
this.actionFetchNotes(his.getNotesDataByProp('discussionsPath'))
|
||||
.then(() => {
|
||||
this.isLoading = false;
|
||||
|
||||
|
@ -78,23 +84,19 @@
|
|||
this.checkLocationHash();
|
||||
});
|
||||
})
|
||||
.catch(() => {
|
||||
Flash('Something went wrong while fetching issue comments. Please try again.');
|
||||
});
|
||||
.catch(() => Flash('Something went wrong while fetching issue comments. Please try again.'));
|
||||
},
|
||||
initPolling() {
|
||||
const { lastFetchedAt } = $('.js-notes-wrapper')[0].dataset;
|
||||
this.setLastFetchedAt(lastFetchedAt);
|
||||
this.setLastFetchedAt(this.getNotesDataByProp('lastFetchedAt'));
|
||||
|
||||
// FIXME: @fatihacet Implement real polling mechanism
|
||||
// TODO: FILIPA: DEAL WITH THIS
|
||||
setInterval(() => {
|
||||
this.poll()
|
||||
.then((res) => {
|
||||
this.setLastFetchedAt(res.lastFetchedAt);
|
||||
})
|
||||
.catch(() => {
|
||||
Flash('Something went wrong while fetching latest comments.');
|
||||
});
|
||||
.catch(() => Flash('Something went wrong while fetching latest comments.'));
|
||||
}, 15000);
|
||||
},
|
||||
bindEventHubListeners() {
|
||||
|
@ -106,6 +108,7 @@
|
|||
.catch(() => Flash('Something went wrong on our end.'));
|
||||
});
|
||||
|
||||
//TODO: FILIPA: REMOVE JQUERY
|
||||
$(document).on('issuable:change', (e, isClosed) => {
|
||||
eventHub.$emit('issueStateChanged', isClosed);
|
||||
});
|
||||
|
@ -120,6 +123,10 @@
|
|||
}
|
||||
},
|
||||
},
|
||||
created() {
|
||||
this.setNotesData(this.notesData);
|
||||
this.setIssueData(this.issueData);
|
||||
},
|
||||
mounted() {
|
||||
this.fetchNotes();
|
||||
this.initPolling();
|
||||
|
@ -135,10 +142,12 @@
|
|||
class="loading">
|
||||
<loading-icon />
|
||||
</div>
|
||||
|
||||
<ul
|
||||
v-if="!isLoading"
|
||||
id="notes-list"
|
||||
class="notes main-notes-list timeline">
|
||||
|
||||
<component
|
||||
v-for="note in notes"
|
||||
:is="getComponentName(note)"
|
||||
|
@ -146,6 +155,7 @@
|
|||
:key="note.id"
|
||||
/>
|
||||
</ul>
|
||||
<issue-comment-form v-if="!isLoading" />
|
||||
|
||||
<issue-comment-form />
|
||||
</div>
|
||||
</template>
|
|
@ -38,8 +38,9 @@
|
|||
:class="{ target: isTargetNote }"
|
||||
class="note system-note timeline-entry">
|
||||
<div class="timeline-entry-inner">
|
||||
<div class="timeline-icon">
|
||||
<span v-html="svg"></span>
|
||||
<div
|
||||
class="timeline-icon"
|
||||
v-html="svg">
|
||||
</div>
|
||||
<div class="timeline-content">
|
||||
<div class="note-header">
|
||||
|
|
|
@ -6,3 +6,5 @@ export const COMMENT = 'comment';
|
|||
export const OPENED = 'opened';
|
||||
export const REOPENED = 'reopened';
|
||||
export const CLOSED = 'closed';
|
||||
export const EMOJI_THUMBSUP = 'thumbsup';
|
||||
export const EMOJI_THUMBSDOWN = 'thumbsdown';
|
||||
|
|
|
@ -1,25 +1,39 @@
|
|||
import Vue from 'vue';
|
||||
import issueNotes from './components/issue_notes.vue';
|
||||
import '../vue_shared/vue_resource_interceptor';
|
||||
import issueNotesApp from './components/issue_notes_app.vue';
|
||||
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
const vm = new Vue({
|
||||
el: '#js-notes',
|
||||
components: {
|
||||
issueNotes,
|
||||
},
|
||||
render(createElement) {
|
||||
return createElement('issue-notes', {
|
||||
attrs: {
|
||||
ref: 'notes',
|
||||
},
|
||||
});
|
||||
},
|
||||
});
|
||||
document.addEventListener('DOMContentLoaded', () => new Vue({
|
||||
el: '#js-vue-notes',
|
||||
components: {
|
||||
issueNotesApp,
|
||||
},
|
||||
data() {
|
||||
const notesDataset = document.getElementById('js-vue-notes').dataset;
|
||||
|
||||
window.issueNotes = {
|
||||
refresh() {
|
||||
vm.$refs.notes.$store.dispatch('poll');
|
||||
},
|
||||
};
|
||||
});
|
||||
return {
|
||||
issueData: JSON.parse(notesDataset.issueData),
|
||||
currentUserData: JSON.parse(notesDataset.currentUserData),
|
||||
notesData: {
|
||||
lastFetchedAt: notesDataset.lastFetchedAt,
|
||||
discussionsPath: notesDataset.discussionsPath,
|
||||
},
|
||||
};
|
||||
},
|
||||
render(createElement) {
|
||||
return createElement('issue-notes-app', {
|
||||
attrs: {
|
||||
ref: 'notes',
|
||||
},
|
||||
props: {
|
||||
issueData: this.issueData,
|
||||
notesData: this.notesData,
|
||||
},
|
||||
});
|
||||
},
|
||||
}));
|
||||
|
||||
// // TODO: FILIPA: FIX THIS
|
||||
// window.issueNotes = {
|
||||
// refresh() {
|
||||
// vm.$refs.notes.$store.dispatch('poll');
|
||||
// },
|
||||
// };
|
||||
|
|
|
@ -7,6 +7,13 @@ import service from '../services/issue_notes_service';
|
|||
import loadAwardsHandler from '../../awards_handler';
|
||||
import sidebarTimeTrackingEventHub from '../../sidebar/event_hub';
|
||||
|
||||
export const setNotesData = ({ commit }, data) => commit(types.SET_NOTES_DATA, data);
|
||||
export const setIssueData = ({ commit }, data) => commit(types.SET_ISSUE_DATA, data);
|
||||
export const setUserData = ({ commit }, data) => commit(types.SET_USER_DATA, data);
|
||||
export const setLastFetchedAt = ({ commit }, data) => commit(types.SET_LAST_FETCHED_AT, data);
|
||||
export const setInitialNotes = ({ commit }, data) => commit(types.SET_INITAL_NOTES, data);
|
||||
export const setTargetNoteHash = ({ commit }, data) => commit(types.SET_TARGET_NOTE_HASH, data);
|
||||
|
||||
export const fetchNotes = ({ commit }, path) => service
|
||||
.fetchNotes(path)
|
||||
.then(res => res.json())
|
||||
|
@ -20,43 +27,31 @@ export const deleteNote = ({ commit }, note) => service
|
|||
commit(types.DELETE_NOTE, note);
|
||||
});
|
||||
|
||||
export const updateNote = ({ commit }, data) => {
|
||||
const { endpoint, note } = data;
|
||||
export const updateNote = ({ commit }, { endpoint, note }) => service
|
||||
.updateNote(endpoint, note)
|
||||
.then(res => res.json())
|
||||
.then((res) => {
|
||||
commit(types.UPDATE_NOTE, res);
|
||||
});
|
||||
|
||||
return service
|
||||
.updateNote(endpoint, note)
|
||||
.then(res => res.json())
|
||||
.then((res) => {
|
||||
commit(types.UPDATE_NOTE, res);
|
||||
});
|
||||
};
|
||||
export const replyToDiscussion = ({ commit }, { endpoint, data }) => service
|
||||
.replyToDiscussion(endpoint, data)
|
||||
.then(res => res.json())
|
||||
.then((res) => {
|
||||
commit(types.ADD_NEW_REPLY_TO_DISCUSSION, res);
|
||||
|
||||
export const replyToDiscussion = ({ commit }, note) => {
|
||||
const { endpoint, data } = note;
|
||||
return res;
|
||||
});
|
||||
|
||||
return service
|
||||
.replyToDiscussion(endpoint, data)
|
||||
.then(res => res.json())
|
||||
.then((res) => {
|
||||
commit(types.ADD_NEW_REPLY_TO_DISCUSSION, res);
|
||||
|
||||
return res;
|
||||
});
|
||||
};
|
||||
|
||||
export const createNewNote = ({ commit }, note) => {
|
||||
const { endpoint, data } = note;
|
||||
|
||||
return service
|
||||
.createNewNote(endpoint, data)
|
||||
.then(res => res.json())
|
||||
.then((res) => {
|
||||
if (!res.errors) {
|
||||
commit(types.ADD_NEW_NOTE, res);
|
||||
}
|
||||
return res;
|
||||
});
|
||||
};
|
||||
export const createNewNote = ({ commit }, { endpoint, data }) => service
|
||||
.createNewNote(endpoint, data)
|
||||
.then(res => res.json())
|
||||
.then((res) => {
|
||||
if (!res.errors) {
|
||||
commit(types.ADD_NEW_NOTE, res);
|
||||
}
|
||||
return res;
|
||||
});
|
||||
|
||||
export const saveNote = ({ commit, dispatch }, noteData) => {
|
||||
const { note } = noteData.data.note;
|
||||
|
@ -91,6 +86,7 @@ export const saveNote = ({ commit, dispatch }, noteData) => {
|
|||
|
||||
if (hasQuickActions && Object.keys(errors).length) {
|
||||
dispatch('poll');
|
||||
|
||||
$('.js-gfm-input').trigger('clear-commands-cache.atwho');
|
||||
Flash('Commands applied', 'notice', $(noteData.flashContainer));
|
||||
}
|
||||
|
@ -136,9 +132,7 @@ export const saveNote = ({ commit, dispatch }, noteData) => {
|
|||
};
|
||||
|
||||
export const poll = ({ commit, state, getters }) => {
|
||||
const { notesPath } = $('.js-notes-wrapper')[0].dataset;
|
||||
|
||||
return service.poll(`${notesPath}?full_data=1`, state.lastFetchedAt)
|
||||
return service.poll(state.notesData.notesPath, state.lastFetchedAt)
|
||||
.then(res => res.json())
|
||||
.then((res) => {
|
||||
if (res.notes.length) {
|
||||
|
@ -160,7 +154,6 @@ export const poll = ({ commit, state, getters }) => {
|
|||
}
|
||||
});
|
||||
}
|
||||
|
||||
return res;
|
||||
});
|
||||
};
|
||||
|
@ -175,20 +168,24 @@ export const toggleAward = ({ commit, getters, dispatch }, data) => {
|
|||
.then(() => {
|
||||
commit(types.TOGGLE_AWARD, { awardName, note });
|
||||
|
||||
if (!skipMutalityCheck && (awardName === 'thumbsup' || awardName === 'thumbsdown')) {
|
||||
const counterAward = awardName === 'thumbsup' ? 'thumbsdown' : 'thumbsup';
|
||||
if (!skipMutalityCheck &&
|
||||
(awardName === constants.EMOJI_THUMBSUP || awardName === constants.EMOJI_THUMBSDOWN)) {
|
||||
const counterAward = awardName === constants.EMOJI_THUMBSUP ?
|
||||
constants.EMOJI_THUMBSDOWN :
|
||||
constants.EMOJI_THUMBSUP;
|
||||
|
||||
const targetNote = getters.notesById[noteId];
|
||||
let amIAwarded = false;
|
||||
let noteHasAward = false;
|
||||
|
||||
targetNote.award_emoji.forEach((a) => {
|
||||
if (a.name === counterAward && a.user.id === window.gon.current_user_id) {
|
||||
amIAwarded = true;
|
||||
noteHasAward = true;
|
||||
}
|
||||
});
|
||||
|
||||
if (amIAwarded) {
|
||||
data.awardName = counterAward;
|
||||
data.skipMutalityCheck = true;
|
||||
if (noteHasAward) {
|
||||
Object.assign(data, { awardName: counterAward });
|
||||
Object.assign(data, { kipMutalityCheck: true });
|
||||
|
||||
dispatch(types.TOGGLE_AWARD, data);
|
||||
}
|
||||
|
@ -197,9 +194,7 @@ export const toggleAward = ({ commit, getters, dispatch }, data) => {
|
|||
};
|
||||
|
||||
export const scrollToNoteIfNeeded = (context, el) => {
|
||||
const isInViewport = gl.utils.isInViewport(el[0]);
|
||||
|
||||
if (!isInViewport) {
|
||||
if (!gl.utils.isInViewport(el[0])) {
|
||||
gl.utils.scrollToElement(el);
|
||||
}
|
||||
};
|
||||
|
|
|
@ -1,10 +1,12 @@
|
|||
export const notes = state => state.notes;
|
||||
|
||||
export const targetNoteHash = state => state.targetNoteHash;
|
||||
export const getNotesDataByProp = state => prop => state.notesData[prop];
|
||||
export const getIssueDataByProp = state => prop => state.notesData[prop];
|
||||
export const getUserDataByProp = state => prop => state.notesData[prop];
|
||||
|
||||
export const notesById = (state) => {
|
||||
const notesByIdObject = {};
|
||||
|
||||
// TODO: FILIPA: TRANSFORM INTO A REDUCE
|
||||
state.notes.forEach((note) => {
|
||||
note.notes.forEach((n) => {
|
||||
notesByIdObject[n.id] = n;
|
||||
|
|
|
@ -11,6 +11,11 @@ export default new Vuex.Store({
|
|||
notes: [],
|
||||
targetNoteHash: null,
|
||||
lastFetchedAt: null,
|
||||
|
||||
// holds endpoints and permissions provided through haml
|
||||
notesData: {},
|
||||
userData: {},
|
||||
issueData: {},
|
||||
},
|
||||
actions,
|
||||
getters,
|
||||
|
|
|
@ -2,6 +2,9 @@ export const ADD_NEW_NOTE = 'ADD_NEW_NOTE';
|
|||
export const ADD_NEW_REPLY_TO_DISCUSSION = 'ADD_NEW_REPLY_TO_DISCUSSION';
|
||||
export const DELETE_NOTE = 'DELETE_NOTE';
|
||||
export const REMOVE_PLACEHOLDER_NOTES = 'REMOVE_PLACEHOLDER_NOTES';
|
||||
export const SET_NOTES_DATA = 'SET_NOTES_DATA';
|
||||
export const SET_ISSUE_DATA = 'SET_ISSUE_DATA';
|
||||
export const SET_USER_DATA = 'SET_USER_DATA';
|
||||
export const SET_INITAL_NOTES = 'SET_INITIAL_NOTES';
|
||||
export const SET_LAST_FETCHED_AT = 'SET_LAST_FETCHED_AT';
|
||||
export const SET_TARGET_NOTE_HASH = 'SET_TARGET_NOTE_HASH';
|
||||
|
|
|
@ -58,6 +58,18 @@ export default {
|
|||
}
|
||||
},
|
||||
|
||||
[types.SET_NOTES_DATA](state, data) {
|
||||
state.notesData = data;
|
||||
},
|
||||
|
||||
[types.SET_ISSUE_DATA](state, data) {
|
||||
state.issueData = data;
|
||||
},
|
||||
|
||||
[types.SET_USER_DATA](state, data) {
|
||||
state.userData = data;
|
||||
},
|
||||
|
||||
[types.SET_INITAL_NOTES](state, notes) {
|
||||
state.notes = notes;
|
||||
},
|
||||
|
|
|
@ -3,16 +3,16 @@
|
|||
= link_to 'Reopen issue', issue_path(@issue, issue: {state_event: :reopen}, format: 'json'), data: {original_text: "Reopen issue", alternative_text: "Comment & reopen issue"}, class: "btn btn-nr btn-reopen btn-comment js-note-target-reopen #{issue_button_visibility(@issue, false)}", title: 'Reopen issue'
|
||||
= link_to 'Close issue', issue_path(@issue, issue: {state_event: :close}, format: 'json'), data: {original_text: "Close issue", alternative_text: "Comment & close issue"}, class: "btn btn-nr btn-close btn-comment js-note-target-close #{issue_button_visibility(@issue, true)}", title: 'Close issue'
|
||||
|
||||
%section.js-notes-wrapper{ data: { discussions_path: discussions_namespace_project_issue_path(@project.namespace, @project, @issue, format: :json), new_session_path: new_session_path(:user, redirect_to_referer: 'yes'), notes_path: notes_url, last_fetched_at: Time.now.to_i } }
|
||||
#js-notes
|
||||
%section
|
||||
#js-vue-notes{ data: { discussions_path: discussions_namespace_project_issue_path(@project.namespace, @project, @issue, format: :json),
|
||||
new_session_path: new_session_path(:user, redirect_to_referer: 'yes'),
|
||||
notes_path: '#{notes_url}?full_data=1', last_fetched_at: Time.now.to_i,
|
||||
issue_data: serialize_issuable(@issue),
|
||||
current_user_data: UserSerializer.new.represent(current_user).to_json }}
|
||||
- content_for :page_specific_javascripts do
|
||||
= webpack_bundle_tag 'common_vue'
|
||||
= webpack_bundle_tag 'notes'
|
||||
|
||||
|
||||
/ #notes{style: "margin-top: 150px"}
|
||||
/ = render 'shared/notes/notes_with_form', :autocomplete => true
|
||||
|
||||
= render "layouts/init_auto_complete"
|
||||
|
||||
:javascript
|
||||
|
|
Loading…
Reference in a new issue